解析f = f(<*>)pure的类型

Sri*_*aic 9 haskell types typechecking applicative

最近我注意到,幽默地liftA可以写成

liftA (<*>) pure
Run Code Online (Sandbox Code Playgroud)

我认为这很整洁,所以开个玩笑,我想我将liftA基于此属性做出新的“定义” :

f = f (<*>) pure
Run Code Online (Sandbox Code Playgroud)

现在,我曾期望这与liftA从未停止过的事情属于同一类型。但是,它无法编译。

• Occurs check: cannot construct the infinite type:
    t ~ (f (a -> b) -> f a -> f b) -> (a1 -> f1 a1) -> t
• In the expression: f (<*>) pure
  In an equation for ‘f’: f = f (<*>) pure
• Relevant bindings include
    f :: (f (a -> b) -> f a -> f b) -> (a1 -> f1 a1) -> t
      (bound at liftA.hs:2:1)
Run Code Online (Sandbox Code Playgroud)

这似乎很明智,我知道编译器有问题。但是,事情变得有些奇怪,因为当我添加注释时:

f :: Applicative f => (a -> b) -> f a -> f b
f = f (<*>) pure
Run Code Online (Sandbox Code Playgroud)

它突然编译。

现在,我最初的怀疑是,我所注释的类型f不是最通用的类​​型,并且通过限制类型使我可以统一事物。但是从类型上看,似乎不是我的类型比编译器试图派生的类型更通用的情况。

这里发生了什么?我在这里有点儿不了解,但是我对编译器在每种情况下的想法以及为什么它在一个而不是另一个中遇到问题感到好奇。

lef*_*out 4

这种混乱是由 Haskell 的类型类和固定类型函数是Applicative(又名阅读器 monad)的实例这一事实引起的。如果你用专门的版本写出来就更清楚了:

\n\n
type Reader a b = a -> b\n\nfmapFn :: (a -> b) -> Reader c a -> Reader c b\nfmapFn = fmap\n    -- \xe2\x89\xa1 liftA\n    -- \xe2\x89\xa1 (.)\n\nfmap\' :: Applicative f => (a -> b) -> f a -> f b\nfmap\' = fmapFn (<*>) pure\n      \xe2\x89\xa1 (<*>) . pure\n      \xe2\x89\xa1 \\\xcf\x86 -> (<*>) (pure \xcf\x86)\n      \xe2\x89\xa1 \\\xcf\x86 fa -> pure \xcf\x86 <*> fa\n
Run Code Online (Sandbox Code Playgroud)\n\n

此时就需要适用法律

\n\n
fmap f x = pure f <*> x\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以

\n\n
 fmap\' \xe2\x89\xa1 \\\xcf\x86 fa -> fmap \xcf\x86 fa\n       \xe2\x89\xa1 fmap\n
Run Code Online (Sandbox Code Playgroud)\n\n

。但重点是,在定义中fmap\' = fmap\' (<*>) pure(<*>)pure属于您希望它最终起作用的函子,但您fmap\'正在使用的实际上始终属于函数函子。在 Haskell 中这没问题:毕竟定义多态的,所以如果顶层知道如何对所有函子执行此操作,那么您当然也可以将它用于函数函子。(不考虑由于循环依赖而导致的不终止问题...)\n但是,因为您正在以以下形式定义它fmap\' = ...态性限制开始生效:如果您 fmap\' = fmap\' (<*>) pure在顶层没有签名的情况下编写,编译器尝试找到一个应该适用的具体类型,特别是单个具体函子。但无论您选择哪种具体类型,这都将与fmapFn您尝试使用的类型不同。因此,该定义仅使用强制其多态的显式签名进行编译(或者,使用标志-XNoMonomorphismRestriction,这会导致编译器在没有显式指令的情况下选择多态类型)

\n\n

编辑令人惊讶的是,事实证明,并不是态性限制试图使类型的多态性低于必要的程度。为了弄清楚它是什么,让我们尝试找到一个具有相同问题的更简单的示例。第一次尝试:

\n\n
fromFloat :: RealFrac a => Float -> a\ntoFloat :: RealFrac a => a -> Float\nfromFloat = realToFrac\ntoFloat   = realToFrac\n\ns = fromFloat . s . toFloat\n
Run Code Online (Sandbox Code Playgroud)\n\n

(我Float之所以选择它,是因为它不是default编译器可能自行选择的类型。)\n
结果编译得很好,但不是最通用的类​​型

\n\n
s\' :: (RealFrac a, RealFrac b) => a -> b\ns\' = fromFloat . s\' . toFloat\n
Run Code Online (Sandbox Code Playgroud)\n\n

它只是选择更简单的

\n\n
s :: Float -> Float\n
Run Code Online (Sandbox Code Playgroud)\n\n

...无论是否启用单态限制。为什么?我不知道; 我觉得这是一个有趣的问题。

\n

  • 当您使用“NoMonomorphismRestriction”进行编译时,该程序仍然会遇到无限类型错误,因此我认为有关单态限制的说法并不完全正确。 (2认同)