为什么GHC认为这种类型变量不是单射的?

时间:2011-10-23 13:20:17

标签: haskell ghc

我有这段代码:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, KindSignatures, GADTs, FlexibleInstances, FlexibleContexts #-}

class Monad m => Effect p e r m | p e m -> r where
  fin :: p e m -> e -> m r

data ErrorEff :: * -> (* -> *) -> * where 
  CatchError :: (e -> m a) -> ErrorEff ((e -> m a) -> m a) m

instance Monad m => Effect ErrorEff ((e -> m a) -> m a) a m where
  fin (CatchError h) = \f -> f h

这不能编译,在最后一行中出现此类型错误:

Could not deduce (a1 ~ a)
from the context (Monad m)
[...]
or from (((e -> m a) -> m a) ~ ((e1 -> m a1) -> m a1))
[...]

如果我将m更改为[]它编译得很好,那么显然GHC认为m不是单射的。 (虽然它没有像类型系列那样警告注入性。)

我的GHC版本是7.2.1。

修改:如果我将(e -> m a)更改为e,则可以将其更改为m a,但不会更改为(m a -> e)

2 个答案:

答案 0 :(得分:31)

这不是一个错误,但这是一个漫长的故事......

故事

在7.0中曾经有一个名为right的强制构造函数,其工作方式如下:

g : f a ~ f b
---------------
right g : a ~ b

也就是说,如果gf af b之间的强制,则right gab之间的强制。只有在保证f是单射的情况下才会发出声音:否则我们可能合法地拥有f Int ~ f Char,然后我们就可以得出结论Int ~ Char,这将是不好的。

但是,当然,类型同义词和类型族不一定是单射的;例如:

type T a = Int

type family F a :: *
type instance F Int  = Bool
type instance F Char = Bool 

那么这种保证怎么可能呢?嗯,这正是为什么不允许部分应用类型同义词和类型系列的原因。类型同义词和类型族的部分应用可能不是单射的,但饱和的应用(甚至是导致更高类型的应用)总是如此。

当然,对部分应用程序的限制令人讨厌。所以在7.2中,为了向允许部分应用的方向移动(并且因为它简化了强制语言的理论和实现),right构造函数被构造函数nth替换,随附的规则

g : T a1 .. an ~ T b1 .. bn
---------------------------
nth i g : ai ~ bi

也就是说,nth仅适用于两种类型之间的强制g,这两种类型已知是类型构造函数T 的饱和应用程序。理论上,这允许类型同义词和族的部分应用,因为我们不能分解等式,直到我们知道它们在(必然是单射的)类型构造函数的应用程序之间。特别是,nth不适用于强制f a ~ f b,因为f是类型变量,而不是类型构造函数。

有人认为在改变时没有人会注意到,但显然这是错误的!

有趣的是,haskell-cafe message from Daniel Schüssler中概述的Olegian技巧表明,类型系列的实现没有相应改变!问题是像

这样的定义
type family Arg fa
type instance Arg (f a) = a
如果f可能是非单射的,则不应该允许

;在这种情况下,定义甚至没有意义。

后续步骤

我认为正确的做法是恢复right(或类似的东西),因为人们显然想要它!希望这很快就会完成。

与此同时,允许部分应用的类型同义词和系列仍然非常酷。这样做的正确方法(tm)似乎是跟踪类型系统中的注入性:也就是说,每种箭头类型都会以其注入性进行注释。这种方式遇到等式f a ~ f b时,我们可以查看f的类型,以确定将其分解为等式a ~ b是否安全。并非巧合,我目前正在努力设计出这样一个系统。 =)

答案 1 :(得分:2)

我不确定原因,但我将您的测试用例缩减为:

{-# LANGUAGE GADTs #-}

data ErrorEff x where
  CatchError :: m a -> ErrorEff (m a)

fin :: ErrorEff (m a) -> m a
fin (CatchError h) = h

编译在GHC 7.0.3中但不是7.3.20111021。

这绝对是一个编译错误。

更改后编译:

data ErrorEff x where
  CatchError :: x -> ErrorEff x

可以使用记录语法恢复函数“fin”:

data ErrorEff x where
  CatchError :: { fin :: m a } -> ErrorEff (m a)