我想知道,我可以在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
差不多就是这样。我知道这是一个菜鸟问题,但我找不到任何解释这种行为。有人能详细说明一下吗?
我试着在谷歌上搜索这些东西,但搜索一无所获
1条答案
按热度按时间wz3gfoph1#
唉,我能理解你的困惑。这是相当麻烦的解释,所以我会简化一点下面。
在这部分
变量
f
是一个模式参数,因此类型注解被解释为 * 提供 * 该变量的类型。本质上,这里我们说“让f
是a->b
类型的任何函数,对于某些类型a
和b
”。相比之下,在
变量
f
是一个表达式,类型注解现在 * 要求 *f
具有该类型。本质上,我们说的是“检查f
在这里有a->b
类型,否则中止”。然而,情况变得更糟。
a
和b
是什么类型?在正常情况下,Haskell解释整个签名,就好像它是在所有变量上量化的一样。本质上,我们有因此,我们实际上是在说“检查
f
是从任何类型到任何类型的多态函数”。变量a
和b
被称为“刚性类型变量”,这意味着“这些代表任何任意类型,不能假设为特定类型”。例如,写入[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顶级名称提供类型注解:
在这种情况下,不需要添加更多的注解。但是,如果我们真的想这样做,我们可以:
使用这个(非常常见的)扩展,我们要求Haskell使
(f :: a -> b)
引用上面一行中定义的a
和b
。这不再意味着(f :: forall a b . a -> b)
。在GHC 9.* 中,我认为扩展是默认打开的。请注意,只有当我们的类型涉及类型变量时才需要这样做。如果我们写
expression :: Int
,我们永远不会遇到这样的问题。就我个人而言,我希望Haskell98(Haskell的第一个版本)总是需要
forall
,并且永远不会自动泛化类型变量,使量化隐式。目前的状态有点奇怪,但这是20多年的研究和在Haskell98之上添加新扩展的结果,同时试图保持(一些)向后兼容性。