GHC为同一个表达式选择不同的实例?

时间:2011-07-13 10:11:03

标签: haskell ghc typeclass

我想实现一个带有arr - 成员函数的箭头,显示不同类型的函数参数的不同行为,例如arr (\x -> (x,x))应该与arr id的行为不同...

以下是代码:

{-# 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

但是:它显示了以下非常奇怪的行为:

> toStr (\x -> (x,x)) -- that works as expected!
"b -> (b,b)" 
> appMyArr (arr (\x -> (x,x))) () -- but this does'nt!!
(((),()),"a -> b")

在第二个例子中,有人可以解释如何让ghci为表达式b -> (b,b)选择\x -> (x,x) - 实例吗?

2 个答案:

答案 0 :(得分:10)

如果您使用IncoherentInstances,任何事情都可能发生。不再有任何承诺以连贯的方式挑选实例。

答案 1 :(得分:6)

简短的回答是发生这种情况是因为编译器在第一种情况下可以访问比第二种情况更具体的类型信息。

编译arr的定义时,编译器只将函数参数f的类型视为b -> c,因此在考虑调用toStr f时,必须选择仅基于此信息的实例。毕竟,可以使用任何函数调用arr。很明显,它只能选择instance ToPredefinedStr (a -> b)

现在,当我们在toStr (\b -> (b, b))中内联它时,编译器在调用站点上有更多可用信息,并且可以选择更具体的实例。

不,如果您正在考虑使用INLINE pragma,则不会更改实例选择。

对于你想要实现的目标,我能想到的最接近的是限制类型,以便实例选择 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)))

这给出了你想要的结果。

*Main> appMyArr (myArr (\x -> (x,x))) ()
(((),()),"b -> (b,b)")

请注意,这有点脆弱,因为您必须通过传播ToPredefinedStr约束来控制实例选择的位置。例如,如果删除类型签名,此函数将以静默方式更改行为。

foo :: (Arrow a, ToPredefinedStr (b -> c)) => (b -> c) -> a b (c, String)
foo f = appMyArr (myArr f)