考虑以下程序(在Haskell中,但可以是任何HM推断的语言):
x = []
y = x!!0
使用HM(或运行编译器),我们推断:
x :: forall t. [t]
y :: forall a. a
我理解这是如何发生的,通过通常的泛化/实例化规则来玩,但我不确定是否需要像forall a. a
这样的东西。
一个问题是:由于我们在这里有一个越界访问,因此可以排除该程序作为一个有效的例子。相反,我们可以说我们推断的通用类型是程序中出错的标志吗?如果是的话,我们是否可以使用这个“事实”故意在其他情况下无效检查无效程序?
下一个程序甚至可以获得更奇怪的类型:
c = []
d = (c!!0) + (1 :: Int)
推断类型:
c :: forall t. [t]
d :: Int
...虽然d
来自c
!
我们可以在没有排除有效程序的情况下增强HM在这里做得更好吗?
编辑:我怀疑 ed 这是使用部分功能的工件(在这种情况下为!!0
)。但请看:
c = []
d = case c of [] -> 0; (x:_) -> x + (1 :: Int)
现在没有使用部分功能。但是,c :: forall t. [t]
和d :: Int
。
答案 0 :(得分:5)
Hindley-Milner类型的术语并不依赖于其子类的值,仅取决于它们的类型。 HM类型检查器永远不会评估表达式,只会对它们进行类型检查,因此它会将x
视为" a
"的列表,而不是" ; a
"的空列表,就像人们非正式地对您的程序进行类型检查一样。
有些类型系统会将您的程序标记为类型不正确,例如dependent types,但那些没有明确类型声明的类型推断,这是Haskell / ML程序员喜欢的奢侈品之一,感谢HM。
使用HM的扩展名(GADTs)Haskell可以为"安全列表定义类型"
data Empty
data NonEmpty
data SafeList a b where
Nil :: SafeList a Empty
Cons:: a -> SafeList a b -> SafeList a NonEmpty
(!!) :: SafeList a NonEmpty -> Int -> a
-- etc
这会使Nil!!0
出现类型错误。
答案 1 :(得分:3)
我不确定是否需要
forall a. a
。
这是不可取的。通过参数化,这种类型的表达式在评估它时唯一可以做的就是无法通过抛出异常或无限循环来停止。当我们讨论产生“底部”(⊥)的计算时,这就是Haskellers的意思。
如果您正在考虑HM的哪些扩展将排除这些类型,您可以禁止任何类型,当被解释为逻辑公式时,不是重言式。这些功能可以保证为某些输入引起错误。
所以x :: forall a. [a]
会没问题,因为对于任何类型a
,我们确实可以构造一个[a]
类型的值 - 一个空列表!但是,例如,head :: forall a. [a] -> a
不合适,因为我们总是可以从类型a
的值中获取类型[a]
的值 - 因为列表可能为空
然而,对于您的类型越具体,这就越没用了。例如,基本上不能保证Int -> Int
类型的函数。