在学习haskell与Haskell Programming from first principles的关系时,我发现了一个让我困惑的练习。
以下是简短版本:
For the following definition:
a) i :: Num a => a
i = 1
b) Try replacing the type signature with the following:
i :: a
替换给我一个错误:
error:
• No instance for (Num a) arising from the literal ‘1’
Possible fix:
add (Num a) to the context of
the type signature for:
i' :: forall a. a
• In the expression: 1
In an equation for ‘i'’: i' = 1
|
38 | i' = 1
| ^
我或多或少地清楚Num约束是如何产生的。不清楚的是为什么将1
赋给多态变量i'
会产生错误。
为什么这样做:
id 1
而这一个则没有:
i' :: a
i' = 1
id i'
是否可以将更具体的值分配给不太具体的,并且在没有问题的情况下丢失一些类型信息?
2条答案
按热度按时间ffscu2ro1#
这是一个常见的误解。您可能会想到,在OO类语言中,
然后你就可以用
Int
值作为参数给一个需要Num
参数的函数,或者用Num
值作为参数给一个需要Object
参数的函数。但是,这根本不是Haskell的类型类的工作方式。
Num
不是一个值类(就像上面的例子中,它是属于某个子类的所有值的类),而是表示特定数字风格的所有 types 的类。这有什么不同呢?像
1 :: Num a => a
这样的多态文字不会生成一个特定的Num
值,然后将其上转换为一个更通用的类。相反,它期望调用者首先选择一个具体的类型,在其中呈现数字,然后立即在该类型中生成数字,此后该类型永远不会改变。换句话说,多态值有一个隐式类型级参数。
1.应该使用什么类型
a
是明确的。(它不一定需要在这里 * 固定 *:调用者本身也可以是一个多态函数。)1.编译器可以证明此类型
a
有一个Num
示例。在C++中,Haskell类型类/多态文字的类似物不是[sub]类和它们的对象,而是被约束到一个概念的 * 模板 *:
现在,
poly_1
可以用在任何需要满足Num
概念的类型的设置中,即特别是constructible_from
和int
的类型,但不能用在需要一些其他类型的上下文中。(In旧的C++这样的模板只是duck类型的,也就是说,它没有明确要求
Num
设置,但编译器会尝试按原样使用它,然后在注意到1
无法转换为指定类型时给予类型错误。)mkh04yzy2#
tl;dr
声明为
i' :: a
的值i'
必须能够替换任何其他值,无一例外。1
不是这样的值,因为它不能被使用,比如说,在需要String
的地方,只是举一个例子。较长版本
让我们从一个不太有争议的场景开始,在这个场景中,您确实需要类型约束:
这不会编译,因为签名等价于
plus :: forall a. a -> a -> a
,而且RHSx + y
对于x
和y
所在的任何公共类型a
都是有意义的,这显然是不正确的。因此,您可以通过提供一个约束来解决上述问题,该约束保证+
在两个a
之间是可能的,并且可以通过将Num a =>
放在::
之后(或者甚至放弃多态类型而将a
改为Int
)来实现。但是,有些函数不需要对参数进行任何约束,下面是其中的三个:
你可以向这些函数传递任何东西,它们总是能工作的,因为它们的定义没有对这些对象做任何假设,因为它们只是把它们洗牌/丢弃。
同样地,
无法编译,因为
1
不能表示任何类型的值a
。例如,它不能表示String
,而签名i' :: a
表达的思想是,可以将i'
放在任何位置,例如,需要Int
的位置,以及需要泛型Num
的位置,或者在期望String
的情况下等等。换句话说,上面的签名表示您可以在这两个语句中使用
i'
:所以问题是:就像我们发现一些函数的签名不以任何方式约束它们的参数一样,我们是否有一个值存在于你能想到的每一个类型中?
是的,有一些类似的值,这里有两个:
它们都是“bottoms”,或⊥。
(¹)我所说的“可用”是指当你在代码编译的地方编写代码时,代码会继续编译。