Haskell:实例中的等式约束

Cli*_*ton 33 haskell

我正在阅读ClassyPrelude的公告,并且到了这里:

instance (b ~ c, CanFilterFunc b a) => CanFilter (b -> c) a where
    filter = filterFunc
Run Code Online (Sandbox Code Playgroud)

作者随后提到这不起作用:

instance (CanFilterFunc b a) => CanFilter (c -> c) a where
    filter = filterFunc
Run Code Online (Sandbox Code Playgroud)

这对我来说很有意义,因为c它与左边的约束完全无关.

但是,文章中没有提到的内容和我不理解的是为什么这不起作用:

instance (CanFilterFunc b a) => CanFilter (b -> b) a where
    filter = filterFunc
Run Code Online (Sandbox Code Playgroud)

有人可以解释为什么这与第一个提到的定义不同?也许GHC类型推断的一个有效例子会有所帮助吗?

kos*_*kus 51

迈克尔已经在他的博客文章中给出了一个很好的解释,但我会尝试用一个(人为的,但相对较小的)例子来说明它.

我们需要以下扩展:

{-# LANGUAGE FlexibleInstances, TypeFamilies #-}
Run Code Online (Sandbox Code Playgroud)

让我们CanFilter用一个参数定义一个比简单更简单的类.我正在定义该类的两个副本,因为我想证明两个实例之间的行为差​​异:

class Twice1 f where
  twice1 :: f -> f

class Twice2 f where
  twice2 :: f -> f
Run Code Online (Sandbox Code Playgroud)

现在,让我们为每个类定义一个实例.因为Twice1,我们将类型变量直接修复为相同,因为Twice2我们允许它们不同,但添加了相等约束.

instance Twice1 (a -> a) where
  twice1 f = f . f

instance (a ~ b) => Twice2 (a -> b) where
  twice2 f = f . f
Run Code Online (Sandbox Code Playgroud)

为了显示差异,让我们定义另一个重载函数,如下所示:

class Example a where
  transform :: Int -> a

instance Example Int where
  transform n = n + 1

instance Example Char where
  transform _ = 'x'
Run Code Online (Sandbox Code Playgroud)

现在我们处于可以看到差异的地步.一旦我们定义

apply1 x = twice1 transform x
apply2 x = twice2 transform x
Run Code Online (Sandbox Code Playgroud)

并向GHC询问推断类型,我们得到了

apply1 :: (Example a, Twice1 (Int -> a)) => Int -> a
apply2 :: Int -> Int
Run Code Online (Sandbox Code Playgroud)

这是为什么?好吧,Twice1当函数的源类型和目标类型相同时,仅触发实例.对于transform和给定的上下文,我们不知道.GHC将仅在右侧匹配时应用实例,因此我们留下未解决的上下文.如果我们试着说apply1 0,会出现类型错误,说仍然没有足够的信息来解决重载问题.我们必须明确指定Int在这种情况下要通过的结果类型.

但是,Twice2实例是针对任何函数类型的.GHC将立即解决它(GHC永远不会回溯,所以如果一个实例明显匹配,它总是被选中),然后尝试建立前提条件:在这种情况下,相等约束,然后强制结果类型,Int并允许我们也解决Example约束.我们可以说apply2 0没有其他类型的注释.

所以这是关于GHC实例解析的一个相当微妙的观点,这里的等式约束帮助GHC的类型检查器以一种需要用户更少类型注释的方式.