GHC chooses different instances for the same expression?
I want to implement an arrow with an arr
-member-function showing a different behavior for function arguments with different types, for instance arr (\x -> (x,x))
should behave differently from arr id
...
Here's the code:
{-# LANGUAGE Arrows, OverlappingInstances, IncoherentInstances, FlexibleInstances#-}
import Control.Arrow
import Control.Category
import Prelude hiding (id, (.))
class ToPredefinedStr a where
toStr :: a -> String
instance ToPredefinedStr ((->) b (b,b)) where
toStr _ = "b -> (b,b)"
instance ToPredefinedStr (a -> (b,c)) where
toStr _ = "a -> (b,c)"
instance ToPredefinedStr ((a,b) -> c) where
toStr _ = "(a,b) -> c"
instance ToPredefinedStr (a -> b) where
toStr _ = "a -> b"
newtype MyArrow a b c = MA (a b (c, String))
instance (Category a, Arrow a) => Category (MyArrow a) where
-- irrelevant for this example ...
instance (Arrow a) => Arrow (MyArrow a) where
arr f = MA (arr (\x -> (f x, toStr f)))
appMyArr (MA a) = a
But: It shows the following very strange behavor:
> toStr (\x -> (x,x)) -- that works as expected!
"b -> (b,b)"
> appMyArr (arr (\x -> (x,x))) () -- but this does'nt!!
(((),()),"a -> b")
Can anyone explain how to get开发者_如何转开发 ghci to choose the b -> (b,b)
-instance for the expression \x -> (x,x)
in the second example?
If you use IncoherentInstances
anything can happen. There is no longer any promise the instances are picked in a coherent way.
The short answer is that this happens because the compiler has access to more specific type information in the first case than in the second.
When compiling your definition of arr
, the compiler only sees the type of the function argument f
as b -> c
, so when considering the call toStr f
it has to choose an instance based on only this information. After all, arr
might be called with any function. It is clear that it can only choose the instance ToPredefinedStr (a -> b)
.
Now, when we inline it like in toStr (\b -> (b, b))
, the compiler has more information available at the call site, and can choose the more specific instance.
And no, using INLINE
pragmas won't change the instance selection if you were thinking of that.
For what you're trying to achieve, the closest I can think of would be to restrict the types so that the instance selection will happen outside arr
:
{-# LANGUAGE FlexibleContexts, ... #-}
class FancyArrow a where
myArr :: (ToPredefinedStr (b -> c)) => (b -> c) -> a b c
...
instance (Arrow a) => FancyArrow (MyArrow a) where
myArr f = MA (arr (\x -> (f x, toStr f)))
This gives the result you wanted.
*Main> appMyArr (myArr (\x -> (x,x))) ()
(((),()),"b -> (b,b)")
Note that this is somewhat brittle, as you have to control where the choice of instance is made by propagating the ToPredefinedStr
constraint. For example, this function silently changes behavior if you remove the type signature.
foo :: (Arrow a, ToPredefinedStr (b -> c)) => (b -> c) -> a b (c, String)
foo f = appMyArr (myArr f)
精彩评论