Haskell 解析'f=f(<;*>;)纯的类型`

Haskell 解析'f=f(<;*>;)纯的类型`,haskell,types,typechecking,applicative,Haskell,Types,Typechecking,Applicative,最近我注意到幽默的生活可以写成 liftA (<*>) 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

最近我注意到幽默的生活可以写成

liftA (<*>) 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)
•发生检查:无法构造无限类型:
t~(f(a->b)->fa->fb)->(a1->f1 a1)->t
•在表达式中:f()纯
在“f”的方程式中:f=f()纯
•相关绑定包括
f::(f(a->b)->f a->f b)->(a1->f1 a1)->t
(在liftA绑定。hs:2:1)
这似乎是明智的,我知道编译器是如何存在问题的。但是事情变得有点奇怪,因为当我添加注释时:

f :: Applicative f => (a -> b) -> f a -> f b
f = f (<*>) pure
f::Applicative f=>(a->b)->f a->f b
f=f()纯
它突然编译了

现在,我最初的怀疑是,我所注释的
f
类型不是最通用的类型,通过限制类型,我使统一事物成为可能。然而,从类型来看,情况似乎并非如此,我的类型似乎比编译器试图派生的类型更一般


这是怎么回事?我在这里有点不得而知,但我很好奇编译器在每个场景中都在想什么,以及为什么它在其中一个场景中遇到问题,而在另一个场景中却没有遇到问题。

这种混乱是由Haskell的类型类和来自固定类型的函数是
Applicative
(又称为reader 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'=fmap'()pure
中,
()
pure
属于您希望最终实现此功能的函子,但您使用的
fmap'
实际上始终属于函数函子。这在Haskell中没关系:毕竟定义是多态的,所以如果顶层知道如何对所有函子执行此操作,那么您当然也可以对函数函子使用它。(撇开循环依赖性导致的非终止问题不谈……) 但是,由于您是以
fmap'=…
的形式定义它,因此,如果您编写
fmap'=fmap'()纯
而在顶层没有签名,编译器将尝试找到一个具体的类型,尤其是一个具体的函子。但无论您选择什么具体类型,这都将是一种不同于您尝试使用的
fmapFn
的类型。因此,此定义仅使用显式签名进行编译,该签名强制它是多态的(或者,使用
-XNoMonomorphismRestriction
标志,该标志导致编译器在没有显式指令的情况下选择多态类型)

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

fromFloat :: RealFrac a => Float -> a
toFloat :: RealFrac a => a -> Float
fromFloat = realToFrac
toFloat   = realToFrac

s = fromFloat . s . toFloat
(我选择了
Float
,因为它不是编译器可能自行选择的
默认类型。)

结果表明,这可以很好地编译,但不是最一般的类型

s' :: (RealFrac a, RealFrac b) => a -> b
s' = fromFloat . s' . toFloat
它只是选择了更简单的

s :: Float -> Float

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

这是因为在f的定义主体中使用的f与定义的类型不同。这称为多态递归,Haskell只允许在提供类型签名的情况下使用它。
需要类型签名的原因是多态递归的类型推断在一般情况下是不可判定的

您的类型注释并不更一般,因为它限制了
在相同的应用程序中工作,并且使用相同的
a
。更通用的GHC派生类型允许两种不同的应用程序。因此,您正确地怀疑,通过添加一个(不太一般的)签名,您可以推断出一个非无限类型。当您使用
NoMonomorphismRestriction
编译程序时,该程序仍然会遇到无限类型错误,因此我认为关于单态限制的说法不太正确。有趣。我做了一个简单的例子,它的行为没有可能的那么普遍。你会把这变成一个问题吗?还是我会?你可以继续提问,我想你比我更能把握这个问题。是的,和我对后续问题的回答一样:
fromFloat :: RealFrac a => Float -> a
toFloat :: RealFrac a => a -> Float
fromFloat = realToFrac
toFloat   = realToFrac

s = fromFloat . s . toFloat
s' :: (RealFrac a, RealFrac b) => a -> b
s' = fromFloat . s' . toFloat
s :: Float -> Float