[英]Why type of pure is a -> f a, not (a -> b) -> f (a -> b) on Applicative?
[英]Resolving the type of `f = f (<*>) pure`
最近我注意到,幽默的liftA
可以写成
liftA (<*>) pure
我认为这很整洁,所以作为一个笑话,我想我会根据这个属性制作一个新的“定义” liftA
:
f = f (<*>) pure
现在我曾预料到这将是与从未停止过的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)
这似乎是明智的,我看到编译器有一个问题。 然而,当我添加注释时,事情变得有点奇怪:
f :: Applicative f => (a -> b) -> f a -> f b
f = f (<*>) pure
它突然编译。
现在我的最初的怀疑是,我是注释类型f
与不是最普遍的类型,并且通过限制型我曾使我们能够以统一的事情。 然而,看看类型似乎并非如此,我的类型似乎比编译器试图派生的类型更通用。
这里发生了什么? 我有点超出了我的深度,但我很好奇编译器在每个场景中的想法以及为什么它遇到一个问题而不是另一个问题。
混淆是由Haskell的类型类引起的,并且函数from-fixed-type是Applicative
的实例(也就是读者monad)。 如果你用专门的版本写出它会变得更清楚:
type Reader a b = a -> b
fmapFn :: (a -> b) -> Reader c a -> Reader c b
fmapFn = fmap
-- ≡ liftA
-- ≡ (.)
fmap' :: Applicative f => (a -> b) -> f a -> f b
fmap' = fmapFn (<*>) pure
≡ (<*>) . pure
≡ \φ -> (<*>) (pure φ)
≡ \φ fa -> pure φ <*> fa
在这一点上,它需要适用法律
fmap f x = pure f <*> x
所以
fmap' ≡ \φ fa -> fmap φ fa
≡ fmap
呃 但关键是,在定义fmap' = fmap' (<*>) pure
, (<*>)
和pure
属于你想让它最终工作的fmap'
,但是你实际使用的fmap'
总是属于函数functor。 在Haskell中没问题:毕竟定义是多态的,所以如果顶级知道如何为所有仿函数执行此操作,那么您当然也可以将它用于函数仿函数。 (不考虑由于循环依赖导致的fmap' = ...
问题......)但是,因为你以fmap' = ...
的形式定义它, 单态限制启动:如果你在顶级写入 编译器试图找到一个应该适用的具体类型,特别是一个具体的仿函数。 但是无论你选择哪种具体类型,这都将是你自己尝试使用的fmap' = fmap' (<*>) pure
而没有签名, fmapFn
的不同类型。 因此,此定义仅使用显式签名进行编译,以强制它为多态 (或者,使用 。 -XNoMonomorphismRestriction
标志,这会导致编译器在没有显式指令的情况下选择多态类型)
编辑令人惊讶的是,事实证明, 并不是单形态限制试图使类型不必要的多态性。 为了弄清楚它是什么,让我们试着找到一个带有同样问题的简单例子。 第一次尝试:
fromFloat :: RealFrac a => Float -> a
toFloat :: RealFrac a => a -> Float
fromFloat = realToFrac
toFloat = realToFrac
s = fromFloat . s . toFloat
(我选择了Float
因为它不是编译器可能自己选择的default
类型。)
原来这个编译得很好,但不是最常见的类型
s' :: (RealFrac a, RealFrac b) => a -> b
s' = fromFloat . s' . toFloat
它只是更简单
s :: Float -> Float
...无论是否启用单态限制。 为什么? 我不知道; 我觉得这个问题很有意思。
这是因为在f的定义体中使用的f具有与定义不同的类型。 这称为多态递归,而Haskell只允许在提供类型签名时使用。 需要类型签名的原因是,在一般情况下,多态递归的类型推断是不可判定的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.