开发者

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)
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜