繁体   English   中英

为什么这个eta扩展是必要的?

[英]Why is this eta expansion necessary?

有人可以帮我理解这个/给我一些阅读材料吗? 以下工作正常:

type F a b = Functor f => f a -> f b
fComp :: F b c -> F a b -> F a c
fComp f f' = f . f'

但是如果我改写fComp = (.) ,那么类型检查器会抱怨:

Couldn't match type ‘b0 -> f c’
              with ‘forall (f1 :: * -> *). Functor f1 => f1 b -> f1 c’

(这个具体的例子并不是特别有用;我只是想缩小研究镜头时出现的例子。)

fComp具有更高级别的类型和更高级别类型的类型推断是非常有限的。 如果我们扩展类型同义词,它可能会更容易理解(但更长!)

fComp :: forall f a b c. Functor f => 
                (forall f1. Functor f1 => f1 b -> f1 c) ->
                (forall f2. Functor f2 => f2 a -> f2 b) ->
                (f a -> f c)

在此类型签名中明确指定了较高等级的ff'类型。 这使得类型推断开始已经知道ff'的类型,因此能够将它们与类型统一起来.

但是,如果你摆脱ff' ,那就是那种类型. 必须采取不知道。 不幸的是,系统无法推断出更高级别的类型,因此您会遇到类型错误。

本质上,编译器不能创建更高级别的类型来填充类型推断期间的未知数,并且必须依赖于程序员注释,我们需要名称( ff'类型签名来获取这些注释。

一个更容易理解的例子是更高级别的id函数:

myId :: (forall a. a) -> (forall b. b)

定义myId x = id x编译,但myId = id给出以下错误:

/home/tikhon/Documents/so/eta-expansion-needed.hs:11:8:
    Couldn't match type ‘b’ with ‘forall a. a’
      ‘b’ is a rigid type variable bound by
          the type signature for myId :: (forall a. a) -> b
          at /home/tikhon/Documents/so/eta-expansion-needed.hs:11:1
    Expected type: (forall a. a) -> b
      Actual type: b -> b
    In the expression: id
    In an equation for ‘myId’: myId = id
Failed, modules loaded: none.

(请记住, forall b. (forall a. a) -> b(forall a. a) -> (forall b. b) 。)

让我使用类似System-F的表示法重写示例,其中我们也传递类型。 下面, \\\\代表类型抽象(大型lambda),以及字典抽象。 此外, @代表类型/字典应用程序。

在此之前,请回忆一下(.)的类型:

(.) :: forall a b c . (b -> a) -> (c -> b) -> (c -> a)

这是带注释的代码(小心,不是为了胆小的人):

fComp :: F b c -> F a b -> F a c
fComp (f :: forall f1. Functor f1 => f1 b -> f1 c)
      (f':: forall f2. Functor f2 => f2 a -> f2 b) 
   = \\ ff :: (* -> *) ->
     \\ ffD :: Functor ff ->
     ((.) @ (ff c) @ (ff b) @ (ff a))   -- instantiated composition
     (f  @ ff @ ffD)                    -- first argument of (.)
     (f' @ ff @ ffD)                    -- second argument of (.)

(上面我假装abc是类型常量,以避免进一步的类型级lambda。)

重要的部分:

  • ff'用于特定类型。 也就是说,它们在被送到(.)之前被应用于类型级参数。
  • (.)在类型级别应用于类型( ff c等),它们不是ff'

正如您所看到的,原始代码远非微不足道。 类型推断能够添加所需的类型级lambda和应用程序。 在添加这些之后,我们不能简单地再签订fComp合同。

在pointfree变体中,类型推断需要做的不仅仅是在有意义的情况下。 虽然fComp的第一个参数是F ab类型,但是(.)的第一个参数必须是x -> y的形式,它不会统一为F ab = forall g . ... F ab = forall g . ... 实际上,没有办法成功解决下面的打字尝试:

fComp :: F b c -> F a b -> F a c
fComp 
   = \\ ff :: (* -> *) ->
     \\ ffD :: Functor ff ->
     ((.) @ ???a @ ???b @ ???c)

上面没有???a ,...可以导致想要的类型。

唯一的可能性是实例化隐藏在F xy类型中的forall -quantified变量,但要做到这一点 ,我们需要一个 编译器可能会为您扩展该代码,以便出现点,从理论上讲可以实例化,但实际上不会。

(此外,eta扩展在Haskell中并不总是有效的:例如seq (undefined::()->()) 3循环而seq (\\x->(undefined::()->()) x) 3返回3 )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM