为什么函数类型需要 "wrapped" 才能满足类型检查器的要求?

Why is a function type required to be "wrapped" for the type checker to be satisfied?

以下程序类型检查:

{-# LANGUAGE RankNTypes #-}

import Numeric.AD (grad)

newtype Fun = Fun (forall a. Num a => [a] -> a)

test1 [u, v] = (v - (u * u * u))
test2 [u, v] = ((u * u) + (v * v) - 1)

main = print $ fmap (\(Fun f) -> grad f [1,1]) [Fun test1, Fun test2]

但是这个程序失败了:

main = print $ fmap (\f -> grad f [1,1]) [test1, test2]

类型错误:

Grad.hs:13:33: error:
    • Couldn't match type ‘Integer’
                     with ‘Numeric.AD.Internal.Reverse.Reverse s Integer’
      Expected type: [Numeric.AD.Internal.Reverse.Reverse s Integer]
                     -> Numeric.AD.Internal.Reverse.Reverse s Integer
        Actual type: [Integer] -> Integer
    • In the first argument of ‘grad’, namely ‘f’
      In the expression: grad f [1, 1]
      In the first argument of ‘fmap’, namely ‘(\ f -> grad f [1, 1])’

直觉上,后一个程序看起来是正确的。毕竟, 以下,看似等效的程序确实有效:

main = print $ [grad test1 [1,1], grad test2 [1,1]]

这看起来像是 GHC 类型系统的限制。我想知道 是什么原因导致失败,为什么存在此限制,以及任何可能的 除了包装函数之外的解决方法(根据上面的 Fun)。

(注意:这不是单态限制导致的;编译 使用 NoMonomorphismRestriction 没有帮助。)

类型推断算法不会推断更高级别的类型(-> 左侧带有 forall 的类型)。如果我没记错的话,它变得不可判定。不管怎样,考虑一下这段代码

foo f = (f True, f 'a')

它的类型应该是什么?我们可以

foo :: (forall a. a -> a) -> (Bool, Char)

但我们也可以

foo :: (forall a. a -> Int) -> (Int, Int)

或者,对于任何类型的构造函数 F :: * -> *

foo :: (forall a. a -> F a) -> (F Bool, F Char)

在这里,据我所知,我们找不到主要类型 -- 一种我们可以分配给 foo.

的最一般类型

如果主要类型不存在,类型推断机制只能为 foo 选择一个次优类型,这可能会在以后导致类型错误。这是不好的。相反,GHC 依赖于 Hindley-Milner 风格的类型推理引擎,该引擎得到了极大的扩展以涵盖更高级的 Haskell 类型。这种机制与普通的 Hindley-Milner 不同,它将分配 f 多态类型 提供 用户明确要求,例如通过给 foo 一个签名。

使用像 Fun 这样的包装器新类型也以类似的方式指示 GHC,为 f.

提供多态类型

这是 GHC 类型系统的问题。顺便说一下,它确实是 GHC 的类型系统; Haskell/ML 类语言的原始类型系统不支持更高级别的多态性,更不用说我们在这里使用的谓词多态性了。

问题是为了进行类型检查,我们需要在类型的任何位置支持 foralls。不仅一直聚集在类型的前面(允许类型推断的正常限制)。一旦离开这个区域,类型推断通常就变得不可判定(对于 n 级多态性和更高级别)。在我们的例子中,[test1, test2] 的类型需要是 [forall a. Num a => a -> a] ,考虑到它不适合上面讨论的方案,这是一个问题。这将要求我们使用谓词多态性,之所以这样称呼是因为 a 范围超过其中包含 forall 的类型,因此 a 可以替换为它正在使用的类型。

因此,因此会出现一些行为不当的情况,仅仅是因为问题无法完全解决。 GHC 确实有一些对 n 阶多态性的支持和一些对命令式多态性的支持,但通常最好只使用新类型包装器来获得可靠的行为。据我所知,GHC 也不鼓励使用这个特性,因为很难准确地弄清楚类型推断算法将处理什么。

总而言之,math 说会有不稳定的情况,newtype 包装器是最好的处理方式,即使有些不满意。