每个程序的不同有效类型推导都会导致具有相同动态语义的结果程序。
首先,程序没有类型;表达式、变量和函数具有类型。因此,我认为它的含义是每个程序片段(表达式、变量或函数)必须具有唯一的类型推导。
然后我想知道Haskell(比如Haskell2010)是否真的是一致的?例如,表达式
\x -> x
可以被赋予类型a -> a
,也可以是Int -> Int
。这是否意味着一致性被破坏了?我可以想到两个反驳:
Int -> Int
不是有效的类型推导,术语\x -> x
获得了推断类型a -> a
,严格来说比Int -> Int
更通用。动态语义在两种情况下都相同,只是类型
Int -> Int
不够通用,在某些情况下会被静态拒绝。
这些哪些是真的?还有其他反驳观点吗?
现在让我们考虑类型类,因为一致性经常在这个上下文中使用。
在GHC实现的Haskell中,有几种可能破坏一致性的方法。显然,IncoherentInstances
扩展和相关的INCOHERENT
pragma似乎很相关。孤儿实例也值得一提。
然后还有更加无害的扩展,比如通过 OverlappingInstances 扩展或 Overlapping、Overlaps 和 Overlappable pragmas 实现的重叠实例,但是即使是 MultiParamTypeClasses 和 FlexibleInstances 的组合也可以引起重叠实例。例如:
class A a b where
aorb :: Either a b
instance A () b where
aorb = Left ()
instance A a () where
aorb = Right ()
x :: Either () ()
x = aorb
"
FlexibleInstances
"和"MultiParamTypeClasses
"扩展已包含在GHC2021
中,因此我希望它们不会破坏一致性。但我认为上面的第一点不正确,第二点似乎不适用于这里,因为动态语义确实不同。我还想提到默认系统。请考虑:
main = print (10 ^ 100)
默认情况下,GHC(和可能是Haskell2010?)将默认使用
Integer
来表示10
和100
。因此,结果会打印一个带有一百个零的数字。但是,如果我们现在添加自定义默认规则:default (Int)
main = print (10 ^ 100)
那么,10
和100
都默认为类型Int
,由于包装,这只打印一个零。因此,在不同的上下文中,表达式10 ^ 100
具有不同的动态语义。这是否不连贯?
所以我的问题是:是否有更正式或更详细的连贯性定义,可以解决上述问题?
\x -> x :: a -> a
和\x -> x :: Int -> Int
的明显推导称为相同的——至少,如果我们认为\x -> x :: a -> a
实际上是\x -> x :: forall a. a -> a
的简写,那么就需要进行一步不需要的泛化步骤,而Int -> Int
则不需要。我甚至会认为\x -> x :: Bool -> Bool
和\x -> x :: Int -> Int
的推导是不同的,因为lambda类型规则在这两个推导中将不同的东西放入环境中。 - Daniel Wagner\x -> x
作为全称量化公式forall a. a -> a
和Int -> Int
的术语级别“证明”。但是,这并不完全正确。正如你所指出的那样,该术语通过一系列规则证明了定理,并且通过不同链证明其定理的两个句法相同的术语被正确地视为不同的推导/证明。特别地,x = aorb
是不连贯的,因为同一术语产生了两个不同的推导/证明。 - K. A. Buhr