多态性:常数与函数

gog*_*urt 3 polymorphism haskell types

我是Haskell的新手,在" 第一原理"一书的Haskell编程中遇到了一个有点令人费解的例子.在第6章结束时突然想到以下内容不起作用:

constant :: (Num a) => a
constant = 1.0
Run Code Online (Sandbox Code Playgroud)

但是,以下工作正常:

f :: (Num a) => a -> a
f x = 3*x
Run Code Online (Sandbox Code Playgroud)

我可以输入任何数值x进入函数f,什么都不会破坏.它不仅限于采用整数.这对我来说很直观.但是常量的例子让我感到困惑.

在本书的reddit 线程上解释(释义),常量示例不起作用的原因是类型声明强制值constant只是比不具体的事物Num.因此,试图将值分配给它的是从一个子类NumFractional不洁净.

如果这个解释是正确的,那么我认为这两个例子看起来完全相反吗?在一种情况下,类型声明强制值尽可能通用.在另一种情况下,函数的可接受值可以是实现的任何Num.

谁能让我直截了当?

Dan*_*ner 5

它有时可以帮助读取类型作为两个演员之间玩的游戏,类型的实现者和类型的用户.为了更好地解释这个观点,我们必须引入Haskell默认隐藏的东西:我们将为所有类型变量添加绑定器.所以你的类型实际上会变成:

constant :: forall a. Num a => a
f :: forall a. Num a => a -> a
Run Code Online (Sandbox Code Playgroud)

现在,我们将阅读类型形成规则:

  • forall a. t表示:呼叫者选择一种类型a,游戏继续进行t
  • c => t表示:调用者显示约束c成立,游戏继续为t
  • t -> t'表示:调用者选择一个类型的值,t游戏继续为t'
  • t(其中t是单态类型,例如裸变量Integer或类似的)意味着:实现者生成类型的值a

我们需要一些其他细节才能真正理解这里的内容,所以我会在这里快速说出来:

  • 当我们编写一个没有小数点的数字时,编译器会隐式地将其转换为通过解析该数字而fromInteger应用于Integer生成的调用.我们有fromInteger :: forall a. Num a => Integer -> a.
  • 当我们写一个带小数点的数字时,编译器通过解析该数字隐式地将其转换为fromRational应用于Rational生成的调用.我们有fromRational :: forall a. Fractional a => Rational -> a.
  • 所述Num类包括方法(*) :: forall a. Num a => a -> a -> a.

现在让我们试着慢慢地仔细研究你的两个例子.

constant :: forall a. Num a => a
constant = 1.0 {- = fromRational (1 % 1) -}
Run Code Online (Sandbox Code Playgroud)

所述类型constant:调用者选择一个类型,显示该类型实现Num,然后实现者必须生成该类型的值.现在,实现者试图通过调用来玩自己的游戏fromRational :: Fractional a => Rational -> a.他选择调用者所做的相同类型,然后尝试显示此类型实现Fractional.哎呀!他无法证明这一点,因为调用者向他证明的唯一事情就是a实现Num- 这并不能保证a实现Fractional.荡.因此,constant不允许实现者fromRational以该类型调用.

现在,让我们来看看f:

f :: forall a. Num a => a -> a
f x = 3*x {- = fromInteger 3 * x -}
Run Code Online (Sandbox Code Playgroud)

所述类型f:调用者选择一个类型,显示该类型实现Num,并选择该类型的值.然后,实现者必须生成该类型的另一个值.他打算打自己的游戏做这个(*)fromInteger.特别是,他选择了呼叫者所做的相同类型.但是现在fromInteger并且(*)只要求他证明这种类型是一个例子Num- 所以他传递了打电话给他的证据,并节省了一天!然后他选择Integer 3参数fromInteger,然后选择结果和调用者给他的值作为两个参数(*).每个人都满意,并且实现者可以返回一个新值.

整个论述的要点是:Num两种情况下的约束都强制执行完全相同的事情,即我们选择实例化的任何类型都a必须是Num类的成员.只是因为定义constant = 1.0中的Num内容不足以完成我们编写的操作,而在内容f x = 3*xNum 足以完成我们编写的操作.而且,由于我们为这两件事选择的操作是如此不同,所以一个人工作而另一个不工作也不应该令人惊讶!