Haskell的类型系统功能强大,并且因其数学严谨性和逻辑性而受到欢迎,另一方面,如下所述的天真使我想知道为什么它不能像直觉那样工作?
E.g。为什么Int
无法将Num
转换为x3
,而f1
接受Int
签名Num
?
Prelude> let x1 = 1
Prelude> :t x1
x1 :: Num a => a
Prelude> let x2 = 1 :: Int
Prelude> :t x2
x2 :: Int
Prelude> let x3 = (1 :: Int) :: Num a => a
Couldn't match expected type ‘a1’ with actual type ‘Int’
Prelude> let f1 :: Num a => a -> a; f1 = id
Prelude> :t f1 (1 :: Int)
f1 (1 :: Int) :: Int
Prelude> let f2 :: Int -> Int; f2 = id
Prelude> :t f2 1
f2 1 :: Int
Prelude> let f3 :: Num a => a -> Int; f3 = id
Prelude> let f4 :: Num a => Int -> a; f4 = id
Couldn't match type ‘a’ with ‘Int’
我知道最终应该学习基础理论,例如HM type system轻松处理类型系统,甚至发现了一些不错的着作,例如揭穿它的1,2,3和4。如果你遇到过这个挑战,你还想推荐什么呢?
@EDIT
Prelude> let f5 x5 = x5::Int
Prelude> :t f5
f5 :: Int -> Int
Prelude> let f6 x6 = x6::Num a => a
Couldn't match expected type ‘a1’ with actual type ‘t’
首先,当x6
使用Num
进行注释时,Num
必须是x6
的超类型Num
。但是,如果我们将Num
从Int
向(x6::Int)::Num a => a
转发为x6
,则Num
Int
之后的Num
的连结类型注释将无法统一。因此,
x6
的第一个推断类型!(n & 1)
在这里不满意。
答案 0 :(得分:17)
为什么
上无法将Int
Num
转换为x3
Int
无法转换为Num
,因为Int
是一种类型而Num
是类型。这两种实体之间的差异有望在以下内容中得到明确。
Int
无法转换为其他任何内容,因为Haskell在您使用的意义上没有转换。没有隐式演员表。如果将专用变为某种确定类型的多态类型,会发生什么?但是,明确的类型永远不会自动成为别的东西。
考虑到这一点,让我们考虑你的例子。
Prelude> let x1 = 1
Prelude> :t x1
x1 :: Num a => a
x1
这里是多态,这意味着它可以根据您的使用方式采用不同的类型。这种不确定性可以通过类型变量a
的存在来识别(类型变量,与具体类型不同,不是大写的)。 x1
的类型虽然是多态的,但在某种程度上受约束 Num a
的限制。 Num a => a
可以理解为“具有类型类 Num
的实例的任何类型”,而普通a
则表示“任何类型”。
Prelude> let x2 = 1 :: Int
Prelude> :t x2
x2 :: Int
引入类型注释:: Int
表示请求Int
统一,其类型为1
,Num a => a
。在这种情况下,这只是意味着用a
替换类型变量Int
。鉴于Int
确实有Num
的实例,这是一个有效的移动,并且类型检查器乐意接受它。类型注释专门 1
到Int
的多态类型。
Prelude> let x3 = (1 :: Int) :: Num a => a
Couldn't match expected type ‘a1’ with actual type ‘Int’
1 :: Int
的类型为Int
。第二个注释要求将其与Num a => a
统一起来。然而,这是不可能的。一旦类型被专门化,您就不能“忘记”类型并仅通过提供类型注释来恢复特化。也许你正在考虑OOP向上倾斜;这根本不是一回事。顺便说一下,如果类型检查器已接受x3
,您就可以编写x4 = ((1 :: Int) :: Num a => a) :: Double
,从而将Int
转换为Double
。但是在一般情况下,这种转换无法像这样发生,因为你没有告诉如何进行转换;至于特殊情况,没有任何。 (将Int
转换为Double
当然是可能的,但它需要适当的功能。例如,您可能会发现考虑fromIntegral
的类型与其所涉及的相关性是相关的。)
Prelude> let f1 :: Num a => a -> a; f1 = id
Prelude> :t f1 (1 :: Int)
f1 (1 :: Int) :: Int
这里的原则保持不变。唯一的区别是你必须考虑参数类型和结果如何相互关联。 id
的类型为a -> a
。它专门适用于Num a => a -> a
。传递Int
参数会进一步将其专门化为Int -> Int
,因此您会得到Int
类型的结果。
Prelude> let f2 :: Int -> Int; f2 = id
Prelude> :t f2 1
f2 1 :: Int
f1
有一个多态类型,你通过提供Int
参数来专门化,而f2
具有单态类型,因此不需要专门化它。 id
专门从a -> a
直接发送到Int -> Int
,而1
专门从Num a => a
到Int
,因为您正在将其提供给一个函数期待Int
参数。
Prelude> let f3 :: Num a => a -> Int; f3 = id
Couldn't match type ‘a’ with ‘Int’
在此,您希望将a -> a
的类型id
与Num a => a -> Int
统一起来。但是,如果您使用a
中的Double
替换Num a => a -> Int
,则会获得Double -> Int
,这不可能与a -> a
统一,因为它更改类型而a -> a
没有。 (这是Thomas M. DuBuisson上面评论的要点:你的实现类型与id
的类型不兼容,因为id
不能改变任何类型。)
Prelude> let f4 :: Num a => Int -> a; f4 = id
Couldn't match type ‘a’ with ‘Int’
最后,这就像f3
,除了不匹配发生在结果类型而不是参数的类型上。这一次采用了不同的旋转方式,您无法通过使用Num a => Int -> a
实例(Num
,Int
来确定特定类型来实现Double
功能,等)然后“向上转发”它到Num a => a
,因为没有上升的东西。相反,Num a => Int -> a
必须适用于具有a
实例的任何选择的Num
。