haskell 刚性类型、函数类型和类型声明混合会导致错误

xxslljrj  于 2023-05-29  发布在  其他
关注(0)|答案(1)|浏览(176)

我想知道,我可以在Haskell中使用类型声明,但有时会导致奇怪的结果

apply (f :: a -> b) = f

很好但是

apply f = (f :: a -> b)

Couldn't match expected type ‘a1 -> b1’ with actual type ‘p’
‘p’ is a rigid type variable bound by
the inferred type of apply :: p -> a -> b

差不多就是这样。我知道这是一个菜鸟问题,但我找不到任何解释这种行为。有人能详细说明一下吗?
我试着在谷歌上搜索这些东西,但搜索一无所获

wz3gfoph

wz3gfoph1#

唉,我能理解你的困惑。这是相当麻烦的解释,所以我会简化一点下面。
在这部分

apply (f :: a -> b) = ...

变量f是一个模式参数,因此类型注解被解释为 * 提供 * 该变量的类型。本质上,这里我们说“让fa->b类型的任何函数,对于某些类型ab”。
相比之下,在

apply f = (f :: a -> b)

变量f是一个表达式,类型注解现在 * 要求 * f具有该类型。本质上,我们说的是“检查f在这里有a->b类型,否则中止”。
然而,情况变得更糟。ab是什么类型?在正常情况下,Haskell解释整个签名,就好像它是在所有变量上量化的一样。本质上,我们有

apply f = (f :: forall a b . a -> b)

因此,我们实际上是在说“检查f是从任何类型到任何类型的多态函数”。变量ab被称为“刚性类型变量”,这意味着“这些代表任何任意类型,不能假设为特定类型”。例如,写入[True, False] :: forall a. [a]会引发类型错误,因为推断的类型是[Bool],但签名表明它必须是任何类型的列表,而不仅仅是Bool。该错误将报告刚性a无法与Bool识别。
这反过来又处理类型推断。类型推断看到apply f =并暂时给f一个类型变量f :: p,其中p是一个非刚性类型变量。如果代码是apply f = f && True,那么我们需要p = Bool,但这没关系,因为p不是刚性的,所以我们可以识别它。
apply f = (f :: forall a b . a -> b)中,我们需要p = forall a b . a -> b,但是,唉,类型推断是复杂的,推断算法通常不允许像p这样的类型变量被示例化为多态类型。这就是类型错误。
复杂的理论。在实践中,该怎么办呢?这里有一些拇指规则。
首先,最好始终为out顶级名称提供类型注解:

apply :: (a -> b) -> (a -> b)
apply f = f

在这种情况下,不需要添加更多的注解。但是,如果我们真的想这样做,我们可以:

-- at the beginning of the file:
{-# LANGUAGE ScopedTypeVariables #-}

apply :: forall a b . (a -> b) -> (a -> b)
apply f = (f :: a -> b)

使用这个(非常常见的)扩展,我们要求Haskell使(f :: a -> b)引用上面一行中定义的ab。这不再意味着(f :: forall a b . a -> b)。在GHC 9.* 中,我认为扩展是默认打开的。
请注意,只有当我们的类型涉及类型变量时才需要这样做。如果我们写expression :: Int,我们永远不会遇到这样的问题。
就我个人而言,我希望Haskell98(Haskell的第一个版本)总是需要forall,并且永远不会自动泛化类型变量,使量化隐式。目前的状态有点奇怪,但这是20多年的研究和在Haskell98之上添加新扩展的结果,同时试图保持(一些)向后兼容性。

相关问题