Overlapping instances - how to circumvent them
up vote
2
down vote
favorite
I am a beginner at Haskell, so please be indulgent. For reasons that are not important here, I am trying to define a operator <^> that takes a function and an argument and returns the value of the function by the argument, irrespective of which of the function and the argument came first. In short, I would like to be able to write the following:
foo :: Int -> Int
foo x = x * x
arg :: Int
arg = 2
foo <^> arg -- valid, returns 4
arg <^> foo -- valid, returns 4
I have tried to accomplish that through type families, as follows:
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeFamilies, TypeOperators #-}
class Combine t1 t2 where
type Output t1 t2 :: *
(<^>) :: t1 -> t2 -> Output t1 t2
instance Combine (a->b) a where
type Output (a->b) a = b
f <^> x = f x
instance Combine a (a->b) where
type Output a a->b = b
x <^> f = f x
On this code, GHC throws a Conflicting family instance declarations. My guess is that the overlap GHC complains about occurs when type a->b and type a are the same. I don't know Haskell well enough, but I suspect that with recursive type definitions, one may be able to construct such a situation. I have a couple of questions:
- Since this is a rather remote scenario that will never occur in my application (in particular not with foo and arg above), I was wondering if there was a way of specifying a dummy default instance to use in case of overlap? I have tried the different
OVERLAPSandOVERLAPPINGflags, but they didn't have any effect. - If not, is there a better way of achieving what I want?
Thanks!
haskell
add a comment |
up vote
2
down vote
favorite
I am a beginner at Haskell, so please be indulgent. For reasons that are not important here, I am trying to define a operator <^> that takes a function and an argument and returns the value of the function by the argument, irrespective of which of the function and the argument came first. In short, I would like to be able to write the following:
foo :: Int -> Int
foo x = x * x
arg :: Int
arg = 2
foo <^> arg -- valid, returns 4
arg <^> foo -- valid, returns 4
I have tried to accomplish that through type families, as follows:
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeFamilies, TypeOperators #-}
class Combine t1 t2 where
type Output t1 t2 :: *
(<^>) :: t1 -> t2 -> Output t1 t2
instance Combine (a->b) a where
type Output (a->b) a = b
f <^> x = f x
instance Combine a (a->b) where
type Output a a->b = b
x <^> f = f x
On this code, GHC throws a Conflicting family instance declarations. My guess is that the overlap GHC complains about occurs when type a->b and type a are the same. I don't know Haskell well enough, but I suspect that with recursive type definitions, one may be able to construct such a situation. I have a couple of questions:
- Since this is a rather remote scenario that will never occur in my application (in particular not with foo and arg above), I was wondering if there was a way of specifying a dummy default instance to use in case of overlap? I have tried the different
OVERLAPSandOVERLAPPINGflags, but they didn't have any effect. - If not, is there a better way of achieving what I want?
Thanks!
haskell
2
I don't think your class makes sense.const idandid constare both valid code, so what wouldconst <^> iddo?
– melpomene
Nov 18 at 13:39
wrt your "reasons that are not important", there's a serious use case here: some people want to use(.)operator for postfix function apply, as in most OOP. But there's too much legacy code using it as compose. Can we do both, by looking at the types of the arguments to(.)? There's still theconst.idproblem.
– AntC
Nov 19 at 11:11
add a comment |
up vote
2
down vote
favorite
up vote
2
down vote
favorite
I am a beginner at Haskell, so please be indulgent. For reasons that are not important here, I am trying to define a operator <^> that takes a function and an argument and returns the value of the function by the argument, irrespective of which of the function and the argument came first. In short, I would like to be able to write the following:
foo :: Int -> Int
foo x = x * x
arg :: Int
arg = 2
foo <^> arg -- valid, returns 4
arg <^> foo -- valid, returns 4
I have tried to accomplish that through type families, as follows:
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeFamilies, TypeOperators #-}
class Combine t1 t2 where
type Output t1 t2 :: *
(<^>) :: t1 -> t2 -> Output t1 t2
instance Combine (a->b) a where
type Output (a->b) a = b
f <^> x = f x
instance Combine a (a->b) where
type Output a a->b = b
x <^> f = f x
On this code, GHC throws a Conflicting family instance declarations. My guess is that the overlap GHC complains about occurs when type a->b and type a are the same. I don't know Haskell well enough, but I suspect that with recursive type definitions, one may be able to construct such a situation. I have a couple of questions:
- Since this is a rather remote scenario that will never occur in my application (in particular not with foo and arg above), I was wondering if there was a way of specifying a dummy default instance to use in case of overlap? I have tried the different
OVERLAPSandOVERLAPPINGflags, but they didn't have any effect. - If not, is there a better way of achieving what I want?
Thanks!
haskell
I am a beginner at Haskell, so please be indulgent. For reasons that are not important here, I am trying to define a operator <^> that takes a function and an argument and returns the value of the function by the argument, irrespective of which of the function and the argument came first. In short, I would like to be able to write the following:
foo :: Int -> Int
foo x = x * x
arg :: Int
arg = 2
foo <^> arg -- valid, returns 4
arg <^> foo -- valid, returns 4
I have tried to accomplish that through type families, as follows:
{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses, TypeFamilies, TypeOperators #-}
class Combine t1 t2 where
type Output t1 t2 :: *
(<^>) :: t1 -> t2 -> Output t1 t2
instance Combine (a->b) a where
type Output (a->b) a = b
f <^> x = f x
instance Combine a (a->b) where
type Output a a->b = b
x <^> f = f x
On this code, GHC throws a Conflicting family instance declarations. My guess is that the overlap GHC complains about occurs when type a->b and type a are the same. I don't know Haskell well enough, but I suspect that with recursive type definitions, one may be able to construct such a situation. I have a couple of questions:
- Since this is a rather remote scenario that will never occur in my application (in particular not with foo and arg above), I was wondering if there was a way of specifying a dummy default instance to use in case of overlap? I have tried the different
OVERLAPSandOVERLAPPINGflags, but they didn't have any effect. - If not, is there a better way of achieving what I want?
Thanks!
haskell
haskell
asked Nov 18 at 13:37
Ahmad B
133
133
2
I don't think your class makes sense.const idandid constare both valid code, so what wouldconst <^> iddo?
– melpomene
Nov 18 at 13:39
wrt your "reasons that are not important", there's a serious use case here: some people want to use(.)operator for postfix function apply, as in most OOP. But there's too much legacy code using it as compose. Can we do both, by looking at the types of the arguments to(.)? There's still theconst.idproblem.
– AntC
Nov 19 at 11:11
add a comment |
2
I don't think your class makes sense.const idandid constare both valid code, so what wouldconst <^> iddo?
– melpomene
Nov 18 at 13:39
wrt your "reasons that are not important", there's a serious use case here: some people want to use(.)operator for postfix function apply, as in most OOP. But there's too much legacy code using it as compose. Can we do both, by looking at the types of the arguments to(.)? There's still theconst.idproblem.
– AntC
Nov 19 at 11:11
2
2
I don't think your class makes sense.
const id and id const are both valid code, so what would const <^> id do?– melpomene
Nov 18 at 13:39
I don't think your class makes sense.
const id and id const are both valid code, so what would const <^> id do?– melpomene
Nov 18 at 13:39
wrt your "reasons that are not important", there's a serious use case here: some people want to use
(.) operator for postfix function apply, as in most OOP. But there's too much legacy code using it as compose. Can we do both, by looking at the types of the arguments to (.)? There's still the const.id problem.– AntC
Nov 19 at 11:11
wrt your "reasons that are not important", there's a serious use case here: some people want to use
(.) operator for postfix function apply, as in most OOP. But there's too much legacy code using it as compose. Can we do both, by looking at the types of the arguments to (.)? There's still the const.id problem.– AntC
Nov 19 at 11:11
add a comment |
2 Answers
2
active
oldest
votes
up vote
1
down vote
accepted
This is a bad idea, in my view, but I'll play along.
A possible solution is to switch to functional dependencies. Usually I tend to avoid fundeps in favor of type families, but here they make the instances compile in a simple way.
class Combine t1 t2 r | t1 t2 -> r where
(<^>) :: t1 -> t2 -> r
instance Combine (a->b) a b where
f <^> x = f x
instance Combine a (a->b) b where
x <^> f = f x
Note that this class will likely cause problems during type inference if we use polymorphic functions. This is because, with polymorphic functions, the code can easily become ambiguous.
For instance id <^> id could pick any of the two instances. Above, melpomene already reported const <^> id being ambiguous as well.
The following is weakly related, but I want to share it anyway:
What about type families instead? I tried to experiment a bit, and I just discovered a limitation which I did not know. Consider the closed type family
type family Output a b where
Output (a->b) a = b
Output a (a->b) = b
The code above compiles, but then the type Output a (a->b) is stuck. The second equation does not get applied, as if the first one could potentially match.
Usually, I can understand this in some other scenarios, but here unifying
Output (a' -> b') b' ~ Output a (a -> b)
seems to fail since we would need a ~ (a' -> b') ~ (a' -> a -> b) which is impossible, with finite types. For some reason, GHC does not use this argument (does it pretend infinite types exist in this check? why?)
Anyway, this makes replacing fundeps with type families harder than it could be, it seems. I have no idea about why GHC accepts the fundeps code I posted, yet refuses the OP's code which is essentially the same thing, except using type families.
Reliably excluding infinite types turns out to be rather tricky in the presence of type families.type family Oops where Oops = () ': Oops.
– dfeuer
Nov 18 at 15:06
@dfeuer True, but in the unification problem above I think we havea'->b' ~ a , b'~a -> bwhich involves no type families. I guess it was simpler to simply turn off the occurs check in all the cases, rather than only when type families are still involved.
– chi
Nov 18 at 15:12
@chi Isb' ~ a'; b ~ a'not a valid solution?
– HTNW
Nov 18 at 15:26
Thank you for your quick reply, chi! My application doesn't involve polymorphic functions so this problem won't arise, although I recognize that this is anissue in the long run. I tried type families as well and run in the same problem you describe. I could solve it by adding a constraint like(Output (a->b) a ~ b), but this leads to some unwanted code duplication and, I suspect, more problems down the road.
– Ahmad B
Nov 18 at 16:25
@HTNW No, since with your substitution the first constraint becomes(a'->a') ~ awhich is not generally true.
– chi
Nov 18 at 16:43
|
show 2 more comments
up vote
1
down vote
@chi is close; an approach using either FunDeps or Closed Type Families is possible. But the Combine instances are potentially ambiguous/unifiable just as much as the CTF Output equations.
When chi says the FunDep code is accepted, that's only half-true: GHC plain leads you down the garden path. It will accept the instances but then you find you can't use them/you get weird error messages. See the Users Guide at "potential for overlap".
If you're looking to resolve a potentially ambiguous Combine constraint, you might get an error suggesting you try IncoherentInstances (or INCOHERENT pragma). Don't do that. You have a genuinely incoherent problem; all that will do is defer the problem to somewhere else. It's always possible to avoid Incoherent -- providing you can rejig your instances (as follows) and they're not locked away in libraries.
Notice that because of the potential ambiguity, another Haskell compiler (Hugs) doesn't let you write Combine like that. It has a more correct implementation of Haskell's (not-well-stated) rules.
The answer is to use a sort of overlap where one instance is strictly more specific. You must first decide which you way you want to prefer in case of ambiguity. I'll choose function prefixed to argument:
{-# LANGUAGE UndecidableInstances, TypeFamilies #-}
instance {-# OVERLAPPING #-} (r ~ b)
=> Combine (a->b) a r where ...
instance {-# OVERLAPPABLE #-} (Combine2 t1 t2 r)
=> Combine t1 t2 r where
(<^>) = revApp
class Combine2 t1 t2 r | t1 t2 -> r where
revApp :: t1 -> t2 -> r
instance (b ~ r) => Combine2 a (a->b) r where
revApp x f = f x
Notice that the OVERLAPPABLE instance for Combine has bare tyvars, it's a catch-all so it's always matchable. All the compiler has to do is decide whether some wanted constraint is of the form of the OVERLAPPING instance.
The Combine2 constraint on the OVERLAPPABLE instance is no smaller than the head, so you need UndecidableInstances. Also beware that deferring to Combine2 will mean that if the compiler still can't resolve, you're likely to get puzzling error messages.
Talking of bare tyvars/"always matchable", I've used an additional trick to make the compiler work really hard to improve the types: There's bare r in the head of the instance, with an Equality type improvement constraint (b ~ r) =>. To use the ~, you need to switch on TypeFamilies even though you're not writing any type families.
A CTF approach would be similar. You need a catch-all equation on Output that calls an auxiliary type function. Again you need UndecidableInstances.
add a comment |
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
1
down vote
accepted
This is a bad idea, in my view, but I'll play along.
A possible solution is to switch to functional dependencies. Usually I tend to avoid fundeps in favor of type families, but here they make the instances compile in a simple way.
class Combine t1 t2 r | t1 t2 -> r where
(<^>) :: t1 -> t2 -> r
instance Combine (a->b) a b where
f <^> x = f x
instance Combine a (a->b) b where
x <^> f = f x
Note that this class will likely cause problems during type inference if we use polymorphic functions. This is because, with polymorphic functions, the code can easily become ambiguous.
For instance id <^> id could pick any of the two instances. Above, melpomene already reported const <^> id being ambiguous as well.
The following is weakly related, but I want to share it anyway:
What about type families instead? I tried to experiment a bit, and I just discovered a limitation which I did not know. Consider the closed type family
type family Output a b where
Output (a->b) a = b
Output a (a->b) = b
The code above compiles, but then the type Output a (a->b) is stuck. The second equation does not get applied, as if the first one could potentially match.
Usually, I can understand this in some other scenarios, but here unifying
Output (a' -> b') b' ~ Output a (a -> b)
seems to fail since we would need a ~ (a' -> b') ~ (a' -> a -> b) which is impossible, with finite types. For some reason, GHC does not use this argument (does it pretend infinite types exist in this check? why?)
Anyway, this makes replacing fundeps with type families harder than it could be, it seems. I have no idea about why GHC accepts the fundeps code I posted, yet refuses the OP's code which is essentially the same thing, except using type families.
Reliably excluding infinite types turns out to be rather tricky in the presence of type families.type family Oops where Oops = () ': Oops.
– dfeuer
Nov 18 at 15:06
@dfeuer True, but in the unification problem above I think we havea'->b' ~ a , b'~a -> bwhich involves no type families. I guess it was simpler to simply turn off the occurs check in all the cases, rather than only when type families are still involved.
– chi
Nov 18 at 15:12
@chi Isb' ~ a'; b ~ a'not a valid solution?
– HTNW
Nov 18 at 15:26
Thank you for your quick reply, chi! My application doesn't involve polymorphic functions so this problem won't arise, although I recognize that this is anissue in the long run. I tried type families as well and run in the same problem you describe. I could solve it by adding a constraint like(Output (a->b) a ~ b), but this leads to some unwanted code duplication and, I suspect, more problems down the road.
– Ahmad B
Nov 18 at 16:25
@HTNW No, since with your substitution the first constraint becomes(a'->a') ~ awhich is not generally true.
– chi
Nov 18 at 16:43
|
show 2 more comments
up vote
1
down vote
accepted
This is a bad idea, in my view, but I'll play along.
A possible solution is to switch to functional dependencies. Usually I tend to avoid fundeps in favor of type families, but here they make the instances compile in a simple way.
class Combine t1 t2 r | t1 t2 -> r where
(<^>) :: t1 -> t2 -> r
instance Combine (a->b) a b where
f <^> x = f x
instance Combine a (a->b) b where
x <^> f = f x
Note that this class will likely cause problems during type inference if we use polymorphic functions. This is because, with polymorphic functions, the code can easily become ambiguous.
For instance id <^> id could pick any of the two instances. Above, melpomene already reported const <^> id being ambiguous as well.
The following is weakly related, but I want to share it anyway:
What about type families instead? I tried to experiment a bit, and I just discovered a limitation which I did not know. Consider the closed type family
type family Output a b where
Output (a->b) a = b
Output a (a->b) = b
The code above compiles, but then the type Output a (a->b) is stuck. The second equation does not get applied, as if the first one could potentially match.
Usually, I can understand this in some other scenarios, but here unifying
Output (a' -> b') b' ~ Output a (a -> b)
seems to fail since we would need a ~ (a' -> b') ~ (a' -> a -> b) which is impossible, with finite types. For some reason, GHC does not use this argument (does it pretend infinite types exist in this check? why?)
Anyway, this makes replacing fundeps with type families harder than it could be, it seems. I have no idea about why GHC accepts the fundeps code I posted, yet refuses the OP's code which is essentially the same thing, except using type families.
Reliably excluding infinite types turns out to be rather tricky in the presence of type families.type family Oops where Oops = () ': Oops.
– dfeuer
Nov 18 at 15:06
@dfeuer True, but in the unification problem above I think we havea'->b' ~ a , b'~a -> bwhich involves no type families. I guess it was simpler to simply turn off the occurs check in all the cases, rather than only when type families are still involved.
– chi
Nov 18 at 15:12
@chi Isb' ~ a'; b ~ a'not a valid solution?
– HTNW
Nov 18 at 15:26
Thank you for your quick reply, chi! My application doesn't involve polymorphic functions so this problem won't arise, although I recognize that this is anissue in the long run. I tried type families as well and run in the same problem you describe. I could solve it by adding a constraint like(Output (a->b) a ~ b), but this leads to some unwanted code duplication and, I suspect, more problems down the road.
– Ahmad B
Nov 18 at 16:25
@HTNW No, since with your substitution the first constraint becomes(a'->a') ~ awhich is not generally true.
– chi
Nov 18 at 16:43
|
show 2 more comments
up vote
1
down vote
accepted
up vote
1
down vote
accepted
This is a bad idea, in my view, but I'll play along.
A possible solution is to switch to functional dependencies. Usually I tend to avoid fundeps in favor of type families, but here they make the instances compile in a simple way.
class Combine t1 t2 r | t1 t2 -> r where
(<^>) :: t1 -> t2 -> r
instance Combine (a->b) a b where
f <^> x = f x
instance Combine a (a->b) b where
x <^> f = f x
Note that this class will likely cause problems during type inference if we use polymorphic functions. This is because, with polymorphic functions, the code can easily become ambiguous.
For instance id <^> id could pick any of the two instances. Above, melpomene already reported const <^> id being ambiguous as well.
The following is weakly related, but I want to share it anyway:
What about type families instead? I tried to experiment a bit, and I just discovered a limitation which I did not know. Consider the closed type family
type family Output a b where
Output (a->b) a = b
Output a (a->b) = b
The code above compiles, but then the type Output a (a->b) is stuck. The second equation does not get applied, as if the first one could potentially match.
Usually, I can understand this in some other scenarios, but here unifying
Output (a' -> b') b' ~ Output a (a -> b)
seems to fail since we would need a ~ (a' -> b') ~ (a' -> a -> b) which is impossible, with finite types. For some reason, GHC does not use this argument (does it pretend infinite types exist in this check? why?)
Anyway, this makes replacing fundeps with type families harder than it could be, it seems. I have no idea about why GHC accepts the fundeps code I posted, yet refuses the OP's code which is essentially the same thing, except using type families.
This is a bad idea, in my view, but I'll play along.
A possible solution is to switch to functional dependencies. Usually I tend to avoid fundeps in favor of type families, but here they make the instances compile in a simple way.
class Combine t1 t2 r | t1 t2 -> r where
(<^>) :: t1 -> t2 -> r
instance Combine (a->b) a b where
f <^> x = f x
instance Combine a (a->b) b where
x <^> f = f x
Note that this class will likely cause problems during type inference if we use polymorphic functions. This is because, with polymorphic functions, the code can easily become ambiguous.
For instance id <^> id could pick any of the two instances. Above, melpomene already reported const <^> id being ambiguous as well.
The following is weakly related, but I want to share it anyway:
What about type families instead? I tried to experiment a bit, and I just discovered a limitation which I did not know. Consider the closed type family
type family Output a b where
Output (a->b) a = b
Output a (a->b) = b
The code above compiles, but then the type Output a (a->b) is stuck. The second equation does not get applied, as if the first one could potentially match.
Usually, I can understand this in some other scenarios, but here unifying
Output (a' -> b') b' ~ Output a (a -> b)
seems to fail since we would need a ~ (a' -> b') ~ (a' -> a -> b) which is impossible, with finite types. For some reason, GHC does not use this argument (does it pretend infinite types exist in this check? why?)
Anyway, this makes replacing fundeps with type families harder than it could be, it seems. I have no idea about why GHC accepts the fundeps code I posted, yet refuses the OP's code which is essentially the same thing, except using type families.
answered Nov 18 at 14:09
chi
71.9k279132
71.9k279132
Reliably excluding infinite types turns out to be rather tricky in the presence of type families.type family Oops where Oops = () ': Oops.
– dfeuer
Nov 18 at 15:06
@dfeuer True, but in the unification problem above I think we havea'->b' ~ a , b'~a -> bwhich involves no type families. I guess it was simpler to simply turn off the occurs check in all the cases, rather than only when type families are still involved.
– chi
Nov 18 at 15:12
@chi Isb' ~ a'; b ~ a'not a valid solution?
– HTNW
Nov 18 at 15:26
Thank you for your quick reply, chi! My application doesn't involve polymorphic functions so this problem won't arise, although I recognize that this is anissue in the long run. I tried type families as well and run in the same problem you describe. I could solve it by adding a constraint like(Output (a->b) a ~ b), but this leads to some unwanted code duplication and, I suspect, more problems down the road.
– Ahmad B
Nov 18 at 16:25
@HTNW No, since with your substitution the first constraint becomes(a'->a') ~ awhich is not generally true.
– chi
Nov 18 at 16:43
|
show 2 more comments
Reliably excluding infinite types turns out to be rather tricky in the presence of type families.type family Oops where Oops = () ': Oops.
– dfeuer
Nov 18 at 15:06
@dfeuer True, but in the unification problem above I think we havea'->b' ~ a , b'~a -> bwhich involves no type families. I guess it was simpler to simply turn off the occurs check in all the cases, rather than only when type families are still involved.
– chi
Nov 18 at 15:12
@chi Isb' ~ a'; b ~ a'not a valid solution?
– HTNW
Nov 18 at 15:26
Thank you for your quick reply, chi! My application doesn't involve polymorphic functions so this problem won't arise, although I recognize that this is anissue in the long run. I tried type families as well and run in the same problem you describe. I could solve it by adding a constraint like(Output (a->b) a ~ b), but this leads to some unwanted code duplication and, I suspect, more problems down the road.
– Ahmad B
Nov 18 at 16:25
@HTNW No, since with your substitution the first constraint becomes(a'->a') ~ awhich is not generally true.
– chi
Nov 18 at 16:43
Reliably excluding infinite types turns out to be rather tricky in the presence of type families.
type family Oops where Oops = () ': Oops.– dfeuer
Nov 18 at 15:06
Reliably excluding infinite types turns out to be rather tricky in the presence of type families.
type family Oops where Oops = () ': Oops.– dfeuer
Nov 18 at 15:06
@dfeuer True, but in the unification problem above I think we have
a'->b' ~ a , b'~a -> b which involves no type families. I guess it was simpler to simply turn off the occurs check in all the cases, rather than only when type families are still involved.– chi
Nov 18 at 15:12
@dfeuer True, but in the unification problem above I think we have
a'->b' ~ a , b'~a -> b which involves no type families. I guess it was simpler to simply turn off the occurs check in all the cases, rather than only when type families are still involved.– chi
Nov 18 at 15:12
@chi Is
b' ~ a'; b ~ a' not a valid solution?– HTNW
Nov 18 at 15:26
@chi Is
b' ~ a'; b ~ a' not a valid solution?– HTNW
Nov 18 at 15:26
Thank you for your quick reply, chi! My application doesn't involve polymorphic functions so this problem won't arise, although I recognize that this is anissue in the long run. I tried type families as well and run in the same problem you describe. I could solve it by adding a constraint like
(Output (a->b) a ~ b), but this leads to some unwanted code duplication and, I suspect, more problems down the road.– Ahmad B
Nov 18 at 16:25
Thank you for your quick reply, chi! My application doesn't involve polymorphic functions so this problem won't arise, although I recognize that this is anissue in the long run. I tried type families as well and run in the same problem you describe. I could solve it by adding a constraint like
(Output (a->b) a ~ b), but this leads to some unwanted code duplication and, I suspect, more problems down the road.– Ahmad B
Nov 18 at 16:25
@HTNW No, since with your substitution the first constraint becomes
(a'->a') ~ a which is not generally true.– chi
Nov 18 at 16:43
@HTNW No, since with your substitution the first constraint becomes
(a'->a') ~ a which is not generally true.– chi
Nov 18 at 16:43
|
show 2 more comments
up vote
1
down vote
@chi is close; an approach using either FunDeps or Closed Type Families is possible. But the Combine instances are potentially ambiguous/unifiable just as much as the CTF Output equations.
When chi says the FunDep code is accepted, that's only half-true: GHC plain leads you down the garden path. It will accept the instances but then you find you can't use them/you get weird error messages. See the Users Guide at "potential for overlap".
If you're looking to resolve a potentially ambiguous Combine constraint, you might get an error suggesting you try IncoherentInstances (or INCOHERENT pragma). Don't do that. You have a genuinely incoherent problem; all that will do is defer the problem to somewhere else. It's always possible to avoid Incoherent -- providing you can rejig your instances (as follows) and they're not locked away in libraries.
Notice that because of the potential ambiguity, another Haskell compiler (Hugs) doesn't let you write Combine like that. It has a more correct implementation of Haskell's (not-well-stated) rules.
The answer is to use a sort of overlap where one instance is strictly more specific. You must first decide which you way you want to prefer in case of ambiguity. I'll choose function prefixed to argument:
{-# LANGUAGE UndecidableInstances, TypeFamilies #-}
instance {-# OVERLAPPING #-} (r ~ b)
=> Combine (a->b) a r where ...
instance {-# OVERLAPPABLE #-} (Combine2 t1 t2 r)
=> Combine t1 t2 r where
(<^>) = revApp
class Combine2 t1 t2 r | t1 t2 -> r where
revApp :: t1 -> t2 -> r
instance (b ~ r) => Combine2 a (a->b) r where
revApp x f = f x
Notice that the OVERLAPPABLE instance for Combine has bare tyvars, it's a catch-all so it's always matchable. All the compiler has to do is decide whether some wanted constraint is of the form of the OVERLAPPING instance.
The Combine2 constraint on the OVERLAPPABLE instance is no smaller than the head, so you need UndecidableInstances. Also beware that deferring to Combine2 will mean that if the compiler still can't resolve, you're likely to get puzzling error messages.
Talking of bare tyvars/"always matchable", I've used an additional trick to make the compiler work really hard to improve the types: There's bare r in the head of the instance, with an Equality type improvement constraint (b ~ r) =>. To use the ~, you need to switch on TypeFamilies even though you're not writing any type families.
A CTF approach would be similar. You need a catch-all equation on Output that calls an auxiliary type function. Again you need UndecidableInstances.
add a comment |
up vote
1
down vote
@chi is close; an approach using either FunDeps or Closed Type Families is possible. But the Combine instances are potentially ambiguous/unifiable just as much as the CTF Output equations.
When chi says the FunDep code is accepted, that's only half-true: GHC plain leads you down the garden path. It will accept the instances but then you find you can't use them/you get weird error messages. See the Users Guide at "potential for overlap".
If you're looking to resolve a potentially ambiguous Combine constraint, you might get an error suggesting you try IncoherentInstances (or INCOHERENT pragma). Don't do that. You have a genuinely incoherent problem; all that will do is defer the problem to somewhere else. It's always possible to avoid Incoherent -- providing you can rejig your instances (as follows) and they're not locked away in libraries.
Notice that because of the potential ambiguity, another Haskell compiler (Hugs) doesn't let you write Combine like that. It has a more correct implementation of Haskell's (not-well-stated) rules.
The answer is to use a sort of overlap where one instance is strictly more specific. You must first decide which you way you want to prefer in case of ambiguity. I'll choose function prefixed to argument:
{-# LANGUAGE UndecidableInstances, TypeFamilies #-}
instance {-# OVERLAPPING #-} (r ~ b)
=> Combine (a->b) a r where ...
instance {-# OVERLAPPABLE #-} (Combine2 t1 t2 r)
=> Combine t1 t2 r where
(<^>) = revApp
class Combine2 t1 t2 r | t1 t2 -> r where
revApp :: t1 -> t2 -> r
instance (b ~ r) => Combine2 a (a->b) r where
revApp x f = f x
Notice that the OVERLAPPABLE instance for Combine has bare tyvars, it's a catch-all so it's always matchable. All the compiler has to do is decide whether some wanted constraint is of the form of the OVERLAPPING instance.
The Combine2 constraint on the OVERLAPPABLE instance is no smaller than the head, so you need UndecidableInstances. Also beware that deferring to Combine2 will mean that if the compiler still can't resolve, you're likely to get puzzling error messages.
Talking of bare tyvars/"always matchable", I've used an additional trick to make the compiler work really hard to improve the types: There's bare r in the head of the instance, with an Equality type improvement constraint (b ~ r) =>. To use the ~, you need to switch on TypeFamilies even though you're not writing any type families.
A CTF approach would be similar. You need a catch-all equation on Output that calls an auxiliary type function. Again you need UndecidableInstances.
add a comment |
up vote
1
down vote
up vote
1
down vote
@chi is close; an approach using either FunDeps or Closed Type Families is possible. But the Combine instances are potentially ambiguous/unifiable just as much as the CTF Output equations.
When chi says the FunDep code is accepted, that's only half-true: GHC plain leads you down the garden path. It will accept the instances but then you find you can't use them/you get weird error messages. See the Users Guide at "potential for overlap".
If you're looking to resolve a potentially ambiguous Combine constraint, you might get an error suggesting you try IncoherentInstances (or INCOHERENT pragma). Don't do that. You have a genuinely incoherent problem; all that will do is defer the problem to somewhere else. It's always possible to avoid Incoherent -- providing you can rejig your instances (as follows) and they're not locked away in libraries.
Notice that because of the potential ambiguity, another Haskell compiler (Hugs) doesn't let you write Combine like that. It has a more correct implementation of Haskell's (not-well-stated) rules.
The answer is to use a sort of overlap where one instance is strictly more specific. You must first decide which you way you want to prefer in case of ambiguity. I'll choose function prefixed to argument:
{-# LANGUAGE UndecidableInstances, TypeFamilies #-}
instance {-# OVERLAPPING #-} (r ~ b)
=> Combine (a->b) a r where ...
instance {-# OVERLAPPABLE #-} (Combine2 t1 t2 r)
=> Combine t1 t2 r where
(<^>) = revApp
class Combine2 t1 t2 r | t1 t2 -> r where
revApp :: t1 -> t2 -> r
instance (b ~ r) => Combine2 a (a->b) r where
revApp x f = f x
Notice that the OVERLAPPABLE instance for Combine has bare tyvars, it's a catch-all so it's always matchable. All the compiler has to do is decide whether some wanted constraint is of the form of the OVERLAPPING instance.
The Combine2 constraint on the OVERLAPPABLE instance is no smaller than the head, so you need UndecidableInstances. Also beware that deferring to Combine2 will mean that if the compiler still can't resolve, you're likely to get puzzling error messages.
Talking of bare tyvars/"always matchable", I've used an additional trick to make the compiler work really hard to improve the types: There's bare r in the head of the instance, with an Equality type improvement constraint (b ~ r) =>. To use the ~, you need to switch on TypeFamilies even though you're not writing any type families.
A CTF approach would be similar. You need a catch-all equation on Output that calls an auxiliary type function. Again you need UndecidableInstances.
@chi is close; an approach using either FunDeps or Closed Type Families is possible. But the Combine instances are potentially ambiguous/unifiable just as much as the CTF Output equations.
When chi says the FunDep code is accepted, that's only half-true: GHC plain leads you down the garden path. It will accept the instances but then you find you can't use them/you get weird error messages. See the Users Guide at "potential for overlap".
If you're looking to resolve a potentially ambiguous Combine constraint, you might get an error suggesting you try IncoherentInstances (or INCOHERENT pragma). Don't do that. You have a genuinely incoherent problem; all that will do is defer the problem to somewhere else. It's always possible to avoid Incoherent -- providing you can rejig your instances (as follows) and they're not locked away in libraries.
Notice that because of the potential ambiguity, another Haskell compiler (Hugs) doesn't let you write Combine like that. It has a more correct implementation of Haskell's (not-well-stated) rules.
The answer is to use a sort of overlap where one instance is strictly more specific. You must first decide which you way you want to prefer in case of ambiguity. I'll choose function prefixed to argument:
{-# LANGUAGE UndecidableInstances, TypeFamilies #-}
instance {-# OVERLAPPING #-} (r ~ b)
=> Combine (a->b) a r where ...
instance {-# OVERLAPPABLE #-} (Combine2 t1 t2 r)
=> Combine t1 t2 r where
(<^>) = revApp
class Combine2 t1 t2 r | t1 t2 -> r where
revApp :: t1 -> t2 -> r
instance (b ~ r) => Combine2 a (a->b) r where
revApp x f = f x
Notice that the OVERLAPPABLE instance for Combine has bare tyvars, it's a catch-all so it's always matchable. All the compiler has to do is decide whether some wanted constraint is of the form of the OVERLAPPING instance.
The Combine2 constraint on the OVERLAPPABLE instance is no smaller than the head, so you need UndecidableInstances. Also beware that deferring to Combine2 will mean that if the compiler still can't resolve, you're likely to get puzzling error messages.
Talking of bare tyvars/"always matchable", I've used an additional trick to make the compiler work really hard to improve the types: There's bare r in the head of the instance, with an Equality type improvement constraint (b ~ r) =>. To use the ~, you need to switch on TypeFamilies even though you're not writing any type families.
A CTF approach would be similar. You need a catch-all equation on Output that calls an auxiliary type function. Again you need UndecidableInstances.
answered Nov 19 at 9:33
AntC
542210
542210
add a comment |
add a comment |
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%2f53361478%2foverlapping-instances-how-to-circumvent-them%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
2
I don't think your class makes sense.
const idandid constare both valid code, so what wouldconst <^> iddo?– melpomene
Nov 18 at 13:39
wrt your "reasons that are not important", there's a serious use case here: some people want to use
(.)operator for postfix function apply, as in most OOP. But there's too much legacy code using it as compose. Can we do both, by looking at the types of the arguments to(.)? There's still theconst.idproblem.– AntC
Nov 19 at 11:11