Bidirectional Functional DependenciesLarge-scale design in Haskell?Functional Dependency in HaskellSpeed comparison with Project Euler: C vs Python vs Erlang vs HaskellHaskell functional dependency conflictAbusing the algebra of algebraic data types - why does this work?GHC code generation for type class function callsWhy not be dependently typed?Confused by the meaning of the 'Alternative' type class and its relationship to other type classesMultiParamTypeClasses, FunctionalDependencies, and calling ambiguous functionsWhy does my functional dependency conflict disappear when I expand the definition?
What is this airplane that sits in front of Barringer High School in Newark, NJ?
Print the new site header
In Street Fighter, what does the M stand for in M Bison?
How to best clean this sealed rotary encoder / volume knob?
How can a clan of females defend themselves in the ancient world against wandering bands?
Are intrusions within a foreign embassy considered an act of war?
In a list with unique pairs A, B, how can I sort them so that the last B is the first A in the next pair?
How much steel armor can you wear and still be able to swim?
"Correct me if I'm wrong"
How to modify a string without altering its text properties
How can I restore a master database from its bak file?
How "fast" do astronomical events occur?
Why are there no file insertion syscalls
"Prove that ∂A is closed given ∂A = Cl(A) − Int(A)"
No shading in ContourPlot3D
How is the idea of "girlfriend material" naturally expressed in Russian?
What does it cost to buy a tavern?
What preparations would Hubble have needed to return in a Shuttle?
Is there any possible way to get these hearts as Adult Link?
Is Newton's third law really correct?
How can a warlock learn from a spellbook?
Summing cube roots in fractions
Teferi's Time Twist and Gideon's Sacrifice
What mathematical theory is required for high frequency trading?
Bidirectional Functional Dependencies
Large-scale design in Haskell?Functional Dependency in HaskellSpeed comparison with Project Euler: C vs Python vs Erlang vs HaskellHaskell functional dependency conflictAbusing the algebra of algebraic data types - why does this work?GHC code generation for type class function callsWhy not be dependently typed?Confused by the meaning of the 'Alternative' type class and its relationship to other type classesMultiParamTypeClasses, FunctionalDependencies, and calling ambiguous functionsWhy does my functional dependency conflict disappear when I expand the definition?
.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;
I have a type class that looks a bit like the following:
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
Or at least these are the bits that are important to my question. This class does not compile, and for good reason. The problem with this class is that I could (if I wanted to) do the following:
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
Now if I call g True
there are two separate results one for each instance. And the compiler picks up on this possibility and informs me that my type class is no good.
My issue is that the dependency | a -> b
is not quite what I mean. I don't just mean that you can find a
from b
, but also that you can find b
from a
. That is each type should only ever be a member of Foo
with one other type so we can given one type find the other. Or to put it in yet another way the dependency is bidirectional. Such a functional dependency would prevent me from having Bool
present in two separate instances because the first parameter would be derivable from the second as well as the second from the first.
But I don't know how to express this idea to the compiler.
How can I create a bidirectional functional dependency? Or, more likely, is there a way that I can rephrase my type class to get something that could replace a bidirectional functional dependency?
haskell typeclass functional-dependencies type-level-computation
add a comment |
I have a type class that looks a bit like the following:
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
Or at least these are the bits that are important to my question. This class does not compile, and for good reason. The problem with this class is that I could (if I wanted to) do the following:
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
Now if I call g True
there are two separate results one for each instance. And the compiler picks up on this possibility and informs me that my type class is no good.
My issue is that the dependency | a -> b
is not quite what I mean. I don't just mean that you can find a
from b
, but also that you can find b
from a
. That is each type should only ever be a member of Foo
with one other type so we can given one type find the other. Or to put it in yet another way the dependency is bidirectional. Such a functional dependency would prevent me from having Bool
present in two separate instances because the first parameter would be derivable from the second as well as the second from the first.
But I don't know how to express this idea to the compiler.
How can I create a bidirectional functional dependency? Or, more likely, is there a way that I can rephrase my type class to get something that could replace a bidirectional functional dependency?
haskell typeclass functional-dependencies type-level-computation
You can list multiple ones likeclass Foo a b | a -> b, b -> a where ...
, but that would make the twoinstance Foo ... Bool
s problematic.
– Willem Van Onsem
Jun 1 at 21:33
add a comment |
I have a type class that looks a bit like the following:
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
Or at least these are the bits that are important to my question. This class does not compile, and for good reason. The problem with this class is that I could (if I wanted to) do the following:
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
Now if I call g True
there are two separate results one for each instance. And the compiler picks up on this possibility and informs me that my type class is no good.
My issue is that the dependency | a -> b
is not quite what I mean. I don't just mean that you can find a
from b
, but also that you can find b
from a
. That is each type should only ever be a member of Foo
with one other type so we can given one type find the other. Or to put it in yet another way the dependency is bidirectional. Such a functional dependency would prevent me from having Bool
present in two separate instances because the first parameter would be derivable from the second as well as the second from the first.
But I don't know how to express this idea to the compiler.
How can I create a bidirectional functional dependency? Or, more likely, is there a way that I can rephrase my type class to get something that could replace a bidirectional functional dependency?
haskell typeclass functional-dependencies type-level-computation
I have a type class that looks a bit like the following:
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
Or at least these are the bits that are important to my question. This class does not compile, and for good reason. The problem with this class is that I could (if I wanted to) do the following:
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
Now if I call g True
there are two separate results one for each instance. And the compiler picks up on this possibility and informs me that my type class is no good.
My issue is that the dependency | a -> b
is not quite what I mean. I don't just mean that you can find a
from b
, but also that you can find b
from a
. That is each type should only ever be a member of Foo
with one other type so we can given one type find the other. Or to put it in yet another way the dependency is bidirectional. Such a functional dependency would prevent me from having Bool
present in two separate instances because the first parameter would be derivable from the second as well as the second from the first.
But I don't know how to express this idea to the compiler.
How can I create a bidirectional functional dependency? Or, more likely, is there a way that I can rephrase my type class to get something that could replace a bidirectional functional dependency?
haskell typeclass functional-dependencies type-level-computation
haskell typeclass functional-dependencies type-level-computation
edited Jun 2 at 2:12
Sriotchilism O'Zaic
asked Jun 1 at 21:24
Sriotchilism O'ZaicSriotchilism O'Zaic
910620
910620
You can list multiple ones likeclass Foo a b | a -> b, b -> a where ...
, but that would make the twoinstance Foo ... Bool
s problematic.
– Willem Van Onsem
Jun 1 at 21:33
add a comment |
You can list multiple ones likeclass Foo a b | a -> b, b -> a where ...
, but that would make the twoinstance Foo ... Bool
s problematic.
– Willem Van Onsem
Jun 1 at 21:33
You can list multiple ones like
class Foo a b | a -> b, b -> a where ...
, but that would make the two instance Foo ... Bool
s problematic.– Willem Van Onsem
Jun 1 at 21:33
You can list multiple ones like
class Foo a b | a -> b, b -> a where ...
, but that would make the two instance Foo ... Bool
s problematic.– Willem Van Onsem
Jun 1 at 21:33
add a comment |
3 Answers
3
active
oldest
votes
A bidirectional dependency between a
and b
can be presented as two functional dependencies a -> b
and b -> a
, like:
class Foo a b | a -> b, b -> a where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
So here a
is functional dependent on b
and b
is functional dependent on a
.
For your instance
s however this of course raises an error, since now you defined two different a
s for b ~ Bool
. This will raise an error like:
file.hs:6:10: error:
Functional dependencies conflict between instance declarations:
instance Foo () Bool -- Defined at file.hs:6:10
instance Foo ((), ()) Bool -- Defined at file.hs:11:10
Failed, modules loaded: none.
Because of the functional dependency, you can only define one a
for b ~ Bool
. But this is probably exactly what you are looking for: a mechanism to prevent defining Foo
twice for the same a
, or the same b
.
1
Thank you. That is exactly what I am looking for, the code I was showing was specifically code that I did not want to be possible, that was possible with the single functional dependency, perhaps I should have worded it better.
– Sriotchilism O'Zaic
Jun 2 at 2:06
add a comment |
(This is more a comment than an answer, since it does not address the exact question the OP asked.)
To complement Willem's answer: nowadays we have another way to make GHC accept this class.
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
As GHC suggests in its error message, we can turn on AllowAmbiguousTypes
. The OP noted that then run in troubles if we evaluate something like g False
and there are two matching instances like
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
Indeed, in such case g False
becomes ambiguous. We then have two options.
First, we can forbid having both the instances above by adding a functional dependency b -> a
to the class (as Willem suggested). That makes g False
to be unambiguous (and we do not need the extension in such case).
Alternatively, we can leave both instances in the code, and disambiguate the call g False
using type applications (another extension). For instance, g @() False
chooses the first instance, while g @((),()) False
chooses the second one.
Full code:
-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies,
FlexibleInstances, AllowAmbiguousTypes, TypeApplications #-
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
main :: IO ()
main = print (g @() False, g @((),()) False)
add a comment |
Willem Van Onsem's answer is pretty much exactly what I wanted, but there is another way I realized that might be worth mentioning. To get the intended behavior we can actually split our class up into multiple classes. There are a couple of ways to do this and the best option probably depends on the specifics. But here is one way you could do it for the code from the question:
class Bar b where
g :: b -> Bool
class (Bar b) => Foo a b | a -> b where
f :: a -> Bool
h :: a -> b -> Bool
Now we do still allow us to make two different Foo
instances with the same b
, but we no longer get the ambiguity since g
is now a member of Bar
there must be a single instance across the two.
This can be done in general by moving the functions that might be ambiguous and moving it to a separate type class.
Another way we can use additional type classes to create a second class to enforce the bidirectionality. For the example this would look like:
class Bar a b | b -> a
class (Bar a b) => Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
Here Bar
is acts to make b
dependent on a
preventing us from having the ambiguity. Since Foo
requires Bar
and Bar
allows a
to be derived from b
, any instance of Foo
allows a
to be derived from b
. This is pretty much what I wanted originally, however it is just a slightly more complex version of Willem Van Onsem's answer.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");
StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);
else
createEditor();
);
function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f56410544%2fbidirectional-functional-dependencies%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
A bidirectional dependency between a
and b
can be presented as two functional dependencies a -> b
and b -> a
, like:
class Foo a b | a -> b, b -> a where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
So here a
is functional dependent on b
and b
is functional dependent on a
.
For your instance
s however this of course raises an error, since now you defined two different a
s for b ~ Bool
. This will raise an error like:
file.hs:6:10: error:
Functional dependencies conflict between instance declarations:
instance Foo () Bool -- Defined at file.hs:6:10
instance Foo ((), ()) Bool -- Defined at file.hs:11:10
Failed, modules loaded: none.
Because of the functional dependency, you can only define one a
for b ~ Bool
. But this is probably exactly what you are looking for: a mechanism to prevent defining Foo
twice for the same a
, or the same b
.
1
Thank you. That is exactly what I am looking for, the code I was showing was specifically code that I did not want to be possible, that was possible with the single functional dependency, perhaps I should have worded it better.
– Sriotchilism O'Zaic
Jun 2 at 2:06
add a comment |
A bidirectional dependency between a
and b
can be presented as two functional dependencies a -> b
and b -> a
, like:
class Foo a b | a -> b, b -> a where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
So here a
is functional dependent on b
and b
is functional dependent on a
.
For your instance
s however this of course raises an error, since now you defined two different a
s for b ~ Bool
. This will raise an error like:
file.hs:6:10: error:
Functional dependencies conflict between instance declarations:
instance Foo () Bool -- Defined at file.hs:6:10
instance Foo ((), ()) Bool -- Defined at file.hs:11:10
Failed, modules loaded: none.
Because of the functional dependency, you can only define one a
for b ~ Bool
. But this is probably exactly what you are looking for: a mechanism to prevent defining Foo
twice for the same a
, or the same b
.
1
Thank you. That is exactly what I am looking for, the code I was showing was specifically code that I did not want to be possible, that was possible with the single functional dependency, perhaps I should have worded it better.
– Sriotchilism O'Zaic
Jun 2 at 2:06
add a comment |
A bidirectional dependency between a
and b
can be presented as two functional dependencies a -> b
and b -> a
, like:
class Foo a b | a -> b, b -> a where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
So here a
is functional dependent on b
and b
is functional dependent on a
.
For your instance
s however this of course raises an error, since now you defined two different a
s for b ~ Bool
. This will raise an error like:
file.hs:6:10: error:
Functional dependencies conflict between instance declarations:
instance Foo () Bool -- Defined at file.hs:6:10
instance Foo ((), ()) Bool -- Defined at file.hs:11:10
Failed, modules loaded: none.
Because of the functional dependency, you can only define one a
for b ~ Bool
. But this is probably exactly what you are looking for: a mechanism to prevent defining Foo
twice for the same a
, or the same b
.
A bidirectional dependency between a
and b
can be presented as two functional dependencies a -> b
and b -> a
, like:
class Foo a b | a -> b, b -> a where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
So here a
is functional dependent on b
and b
is functional dependent on a
.
For your instance
s however this of course raises an error, since now you defined two different a
s for b ~ Bool
. This will raise an error like:
file.hs:6:10: error:
Functional dependencies conflict between instance declarations:
instance Foo () Bool -- Defined at file.hs:6:10
instance Foo ((), ()) Bool -- Defined at file.hs:11:10
Failed, modules loaded: none.
Because of the functional dependency, you can only define one a
for b ~ Bool
. But this is probably exactly what you are looking for: a mechanism to prevent defining Foo
twice for the same a
, or the same b
.
edited Jun 1 at 21:41
answered Jun 1 at 21:37
Willem Van OnsemWillem Van Onsem
163k17165254
163k17165254
1
Thank you. That is exactly what I am looking for, the code I was showing was specifically code that I did not want to be possible, that was possible with the single functional dependency, perhaps I should have worded it better.
– Sriotchilism O'Zaic
Jun 2 at 2:06
add a comment |
1
Thank you. That is exactly what I am looking for, the code I was showing was specifically code that I did not want to be possible, that was possible with the single functional dependency, perhaps I should have worded it better.
– Sriotchilism O'Zaic
Jun 2 at 2:06
1
1
Thank you. That is exactly what I am looking for, the code I was showing was specifically code that I did not want to be possible, that was possible with the single functional dependency, perhaps I should have worded it better.
– Sriotchilism O'Zaic
Jun 2 at 2:06
Thank you. That is exactly what I am looking for, the code I was showing was specifically code that I did not want to be possible, that was possible with the single functional dependency, perhaps I should have worded it better.
– Sriotchilism O'Zaic
Jun 2 at 2:06
add a comment |
(This is more a comment than an answer, since it does not address the exact question the OP asked.)
To complement Willem's answer: nowadays we have another way to make GHC accept this class.
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
As GHC suggests in its error message, we can turn on AllowAmbiguousTypes
. The OP noted that then run in troubles if we evaluate something like g False
and there are two matching instances like
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
Indeed, in such case g False
becomes ambiguous. We then have two options.
First, we can forbid having both the instances above by adding a functional dependency b -> a
to the class (as Willem suggested). That makes g False
to be unambiguous (and we do not need the extension in such case).
Alternatively, we can leave both instances in the code, and disambiguate the call g False
using type applications (another extension). For instance, g @() False
chooses the first instance, while g @((),()) False
chooses the second one.
Full code:
-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies,
FlexibleInstances, AllowAmbiguousTypes, TypeApplications #-
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
main :: IO ()
main = print (g @() False, g @((),()) False)
add a comment |
(This is more a comment than an answer, since it does not address the exact question the OP asked.)
To complement Willem's answer: nowadays we have another way to make GHC accept this class.
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
As GHC suggests in its error message, we can turn on AllowAmbiguousTypes
. The OP noted that then run in troubles if we evaluate something like g False
and there are two matching instances like
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
Indeed, in such case g False
becomes ambiguous. We then have two options.
First, we can forbid having both the instances above by adding a functional dependency b -> a
to the class (as Willem suggested). That makes g False
to be unambiguous (and we do not need the extension in such case).
Alternatively, we can leave both instances in the code, and disambiguate the call g False
using type applications (another extension). For instance, g @() False
chooses the first instance, while g @((),()) False
chooses the second one.
Full code:
-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies,
FlexibleInstances, AllowAmbiguousTypes, TypeApplications #-
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
main :: IO ()
main = print (g @() False, g @((),()) False)
add a comment |
(This is more a comment than an answer, since it does not address the exact question the OP asked.)
To complement Willem's answer: nowadays we have another way to make GHC accept this class.
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
As GHC suggests in its error message, we can turn on AllowAmbiguousTypes
. The OP noted that then run in troubles if we evaluate something like g False
and there are two matching instances like
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
Indeed, in such case g False
becomes ambiguous. We then have two options.
First, we can forbid having both the instances above by adding a functional dependency b -> a
to the class (as Willem suggested). That makes g False
to be unambiguous (and we do not need the extension in such case).
Alternatively, we can leave both instances in the code, and disambiguate the call g False
using type applications (another extension). For instance, g @() False
chooses the first instance, while g @((),()) False
chooses the second one.
Full code:
-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies,
FlexibleInstances, AllowAmbiguousTypes, TypeApplications #-
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
main :: IO ()
main = print (g @() False, g @((),()) False)
(This is more a comment than an answer, since it does not address the exact question the OP asked.)
To complement Willem's answer: nowadays we have another way to make GHC accept this class.
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
As GHC suggests in its error message, we can turn on AllowAmbiguousTypes
. The OP noted that then run in troubles if we evaluate something like g False
and there are two matching instances like
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
Indeed, in such case g False
becomes ambiguous. We then have two options.
First, we can forbid having both the instances above by adding a functional dependency b -> a
to the class (as Willem suggested). That makes g False
to be unambiguous (and we do not need the extension in such case).
Alternatively, we can leave both instances in the code, and disambiguate the call g False
using type applications (another extension). For instance, g @() False
chooses the first instance, while g @((),()) False
chooses the second one.
Full code:
-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies,
FlexibleInstances, AllowAmbiguousTypes, TypeApplications #-
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
main :: IO ()
main = print (g @() False, g @((),()) False)
answered Jun 2 at 8:26
chichi
79.6k288151
79.6k288151
add a comment |
add a comment |
Willem Van Onsem's answer is pretty much exactly what I wanted, but there is another way I realized that might be worth mentioning. To get the intended behavior we can actually split our class up into multiple classes. There are a couple of ways to do this and the best option probably depends on the specifics. But here is one way you could do it for the code from the question:
class Bar b where
g :: b -> Bool
class (Bar b) => Foo a b | a -> b where
f :: a -> Bool
h :: a -> b -> Bool
Now we do still allow us to make two different Foo
instances with the same b
, but we no longer get the ambiguity since g
is now a member of Bar
there must be a single instance across the two.
This can be done in general by moving the functions that might be ambiguous and moving it to a separate type class.
Another way we can use additional type classes to create a second class to enforce the bidirectionality. For the example this would look like:
class Bar a b | b -> a
class (Bar a b) => Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
Here Bar
is acts to make b
dependent on a
preventing us from having the ambiguity. Since Foo
requires Bar
and Bar
allows a
to be derived from b
, any instance of Foo
allows a
to be derived from b
. This is pretty much what I wanted originally, however it is just a slightly more complex version of Willem Van Onsem's answer.
add a comment |
Willem Van Onsem's answer is pretty much exactly what I wanted, but there is another way I realized that might be worth mentioning. To get the intended behavior we can actually split our class up into multiple classes. There are a couple of ways to do this and the best option probably depends on the specifics. But here is one way you could do it for the code from the question:
class Bar b where
g :: b -> Bool
class (Bar b) => Foo a b | a -> b where
f :: a -> Bool
h :: a -> b -> Bool
Now we do still allow us to make two different Foo
instances with the same b
, but we no longer get the ambiguity since g
is now a member of Bar
there must be a single instance across the two.
This can be done in general by moving the functions that might be ambiguous and moving it to a separate type class.
Another way we can use additional type classes to create a second class to enforce the bidirectionality. For the example this would look like:
class Bar a b | b -> a
class (Bar a b) => Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
Here Bar
is acts to make b
dependent on a
preventing us from having the ambiguity. Since Foo
requires Bar
and Bar
allows a
to be derived from b
, any instance of Foo
allows a
to be derived from b
. This is pretty much what I wanted originally, however it is just a slightly more complex version of Willem Van Onsem's answer.
add a comment |
Willem Van Onsem's answer is pretty much exactly what I wanted, but there is another way I realized that might be worth mentioning. To get the intended behavior we can actually split our class up into multiple classes. There are a couple of ways to do this and the best option probably depends on the specifics. But here is one way you could do it for the code from the question:
class Bar b where
g :: b -> Bool
class (Bar b) => Foo a b | a -> b where
f :: a -> Bool
h :: a -> b -> Bool
Now we do still allow us to make two different Foo
instances with the same b
, but we no longer get the ambiguity since g
is now a member of Bar
there must be a single instance across the two.
This can be done in general by moving the functions that might be ambiguous and moving it to a separate type class.
Another way we can use additional type classes to create a second class to enforce the bidirectionality. For the example this would look like:
class Bar a b | b -> a
class (Bar a b) => Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
Here Bar
is acts to make b
dependent on a
preventing us from having the ambiguity. Since Foo
requires Bar
and Bar
allows a
to be derived from b
, any instance of Foo
allows a
to be derived from b
. This is pretty much what I wanted originally, however it is just a slightly more complex version of Willem Van Onsem's answer.
Willem Van Onsem's answer is pretty much exactly what I wanted, but there is another way I realized that might be worth mentioning. To get the intended behavior we can actually split our class up into multiple classes. There are a couple of ways to do this and the best option probably depends on the specifics. But here is one way you could do it for the code from the question:
class Bar b where
g :: b -> Bool
class (Bar b) => Foo a b | a -> b where
f :: a -> Bool
h :: a -> b -> Bool
Now we do still allow us to make two different Foo
instances with the same b
, but we no longer get the ambiguity since g
is now a member of Bar
there must be a single instance across the two.
This can be done in general by moving the functions that might be ambiguous and moving it to a separate type class.
Another way we can use additional type classes to create a second class to enforce the bidirectionality. For the example this would look like:
class Bar a b | b -> a
class (Bar a b) => Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
Here Bar
is acts to make b
dependent on a
preventing us from having the ambiguity. Since Foo
requires Bar
and Bar
allows a
to be derived from b
, any instance of Foo
allows a
to be derived from b
. This is pretty much what I wanted originally, however it is just a slightly more complex version of Willem Van Onsem's answer.
answered Jun 3 at 1:55
Sriotchilism O'ZaicSriotchilism O'Zaic
910620
910620
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f56410544%2fbidirectional-functional-dependencies%23new-answer', 'question_page');
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
You can list multiple ones like
class Foo a b | a -> b, b -> a where ...
, but that would make the twoinstance Foo ... Bool
s problematic.– Willem Van Onsem
Jun 1 at 21:33