Haskell:实例中的等式约束

时间:2012-07-19 03:55:58

标签: haskell

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

instance (b ~ c, CanFilterFunc b a) => CanFilter (b -> c) a where
    filter = filterFunc

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

instance (CanFilterFunc b a) => CanFilter (c -> c) a where
    filter = filterFunc

这对我有意义,因为c与左边的约束完全无关。

然而,文章中没有提及的内容以及我不理解的是为什么这不起作用:

instance (CanFilterFunc b a) => CanFilter (b -> b) a where
    filter = filterFunc

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

2 个答案:

答案 0 :(得分:52)

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

我们需要以下扩展名:

{-# LANGUAGE FlexibleInstances, TypeFamilies #-}

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

class Twice1 f where
  twice1 :: f -> f

class Twice2 f where
  twice2 :: f -> f

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

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

instance (a ~ b) => Twice2 (a -> b) where
  twice2 f = f . f

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

class Example a where
  transform :: Int -> a

instance Example Int where
  transform n = n + 1

instance Example Char where
  transform _ = 'x'

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

apply1 x = twice1 transform x
apply2 x = twice2 transform x

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

apply1 :: (Example a, Twice1 (Int -> a)) => Int -> a
apply2 :: Int -> Int

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

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

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

答案 1 :(得分:0)

完成@kosmikus答案

对purescript同样适用-您需要相等约束才能正确导出类型(您可以在此处http://try.purescript.org尝试)

module Main where

import Prelude

-- copied from https://github.com/purescript/purescript-type-equality/blob/master/src/Type/Equality.purs
class TypeEquals a b | a -> b, b -> a where
  to :: a -> b
  from :: b -> a

instance refl :: TypeEquals a a where
  to a = a
  from a = a

-----------------

class Twice1 f where
  twice1 :: f -> f

class Twice2 f where
  twice2 :: f -> f

instance mytwice1 :: Twice1 (a -> a) where
  twice1 f = f >>> f

instance mytwice2 :: TypeEquals a b => Twice2 (a -> b) where
  twice2 f = f >>> from >>> f

class Example a where
  transform :: Int -> a

instance exampleInt :: Example Int where
  transform n = n + 1

instance exampleChar :: Example Char where
  transform _ = 'x'

{--
-- will raise error
--   No type class instance was found for Main.Twice1 (Int -> t1)

apply1 x = twice1 transform x

-- to resolve error add type declaration
apply1 :: Int -> Int

--}

-- compiles without error and manual type declaration, has type Int -> Int automatically
apply2 x = twice2 transform x

但是在idris中,你不会

module Main

import Prelude

interface Twice f where
  twice : f -> f

Twice (a -> a) where
  twice f = f . f

interface Example a where
  transform : Int -> a

Example Int where
  transform n = n + 1

Example Char where
  transform _ = 'x'

-- run in REPL to see that it derives properly:

-- $ idris src/15_EqualityConstraint_Twice_class.idr
-- *src/15_EqualityConstraint_Twice_class> :t twice transform
-- twice transform : Int -> Int

-- Summary:
-- in idris you dont need equality constaint to derive type of such functions properly