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

phy*_*nfo 2 haskell typeclass ghc

我想实现一个带有arr-member-function 的箭头,显示不同类型的函数参数的不同行为,例如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
Run Code Online (Sandbox Code Playgroud)

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

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

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

aug*_*tss 10

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


ham*_*mar 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)))
Run Code Online (Sandbox Code Playgroud)

这给出了你想要的结果.

*Main> appMyArr (myArr (\x -> (x,x))) ()
(((),()),"b -> (b,b)")
Run Code Online (Sandbox Code Playgroud)

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

foo :: (Arrow a, ToPredefinedStr (b -> c)) => (b -> c) -> a b (c, String)
foo f = appMyArr (myArr f)
Run Code Online (Sandbox Code Playgroud)