【发布时间】:2016-01-03 14:44:32
【问题描述】:
我刚开始向你学习一个 Haskell,我在一个例子中看到了这个,没有解释:
tell :: (Show a) => [a] -> String
这是什么意思,尤其是=>?我知道如果我替换 -> 或删除它,它不会起作用,但我真的不明白为什么。
【问题讨论】:
-
很好的问题(答案的质量证明了这一点),但请坚持阅读这本书——一切很快就会清楚。
我刚开始向你学习一个 Haskell,我在一个例子中看到了这个,没有解释:
tell :: (Show a) => [a] -> String
这是什么意思,尤其是=>?我知道如果我替换 -> 或删除它,它不会起作用,但我真的不明白为什么。
【问题讨论】:
这是另一种看待它的方式。函数的一些参数是不可见的,有些是可见的。 input -> output 类型告诉我们可见的 input 应该作为参数。类型(Constraint) => output 告诉我们需要一些不可见的信息。它们不可互换,因为必须写出可见的论点,而不能写出不可见的论点。无形的参数是由编译器自己弄清楚的(嗯,他听起来像我自己),他坚持要让他们感到困惑:他拒绝被告知它们是什么!
秘密地,这个tell 示例的完整类型是
tell :: forall (a :: *). (Show a) => [a] -> String
我所做的是明确这个a 变量的来源以及它是什么类型的东西。您也可以将其解读为“交易”:tell 提供适用于满足(Show a) 需求的所有类型a。
为了使tell 的用法有意义,它需要三件事。其中两个是不可见的,一个是可见的。也就是说,当您使用tell 时,您使可见参数显式,并且编译器尝试填充不可见部分。让我们更慢地处理这种类型。
tell :: forall (a :: *). -- the type of elements to tell (invisible)
(Show a) => -- how to make a String from one element (invisible)
[a] -> -- the list of elements to be told (visible)
String -- the String made by showing all the elements
所以,当你使用tell,例如,
tell [True, False]
你只给出可见的参数:[True, False] 要告诉的事情列表,编译器会计算出不可见的参数。他知道True 和False 都是Bool 类型的值,所以这意味着
[True, False] :: [Bool]
这就是编译器如何计算出tell类型中的a必须是Bool,使得[a] = [Bool]
(顺便说一下,关于[True, False] :: [Bool]。:: 的左侧,方括号,[..],生成列表值。:: 的右侧,方括号,[..],生成列表的类型。在你看来,它们可能只是在灰色背景上看起来是黑色的,但我的大脑将创造价值的括号涂成红色,将打字的括号涂成蓝色。它们完全不同。我希望我可以在这个网站上进行颜色编码。我离题了。)
所以,现在,另一个不可见的论点必须满足 (Show a) 这个东西,我们现在知道具体是 (Show Bool),因为我们发现 a 是 Bool。我们将类型的这一部分称为“约束”,但实际上它不仅要求事实是真实的,而且还要求存在一些有用的东西。这里要求的东西是有一个功能
show :: Bool -> String
这是在评估tell [True, False]的过程中用于将单个元素True和False转换为Strings的函数。
标识符Show 是类型类的名称,show是该类型类的方法。类型class 指定必须为每个instance 实现的操作接口。该函数的不可见参数是一个记录(或“字典”),它为所讨论的类型(这里是show 的实现)打包了这些操作的实现。如果没有这些信息,编译后的代码将无法完成其工作,但我们不必编写该信息,因为编译器可以(至少在这种情况下)搜索它知道的实例并填写正确的一份工作。
因此,我们不仅有不可见的类型参数(在编译时推断,然后在运行时删除),由小写类型变量发出信号,或更明确地由forall blah .。我们还有类型类操作的不可见实现(在编译时查找,以提供重要的运行时信息)。因此,在推断类型和擦除类型之间会发生一些非常重要的事情:虽然编译器仍然知道类型,但它会使用它们来确定在运行时需要哪些不可见的实现,这样我们就可以侥幸逃脱不是我们自己写的。
缩小,类型中的=> 记录了我们的期望,即编译器将利用类型信息来指导我们不必费心编写的运行时代码的生成。这是一个不错的胜利,就在那里。
类型系统黑客的别有用心。 不可见-可见的区别与可擦-有用的区别在不同的地方,有些人还没有收到。这是经典的 Hindley-Milner 立场,但事实是这些区别是正交的,人们越早学会享受这一点越好。
【讨论】:
=>首先在Chapter 3 - Types and Typeclasses中介绍和解释:
== 函数的类型签名是什么?
ghci> :t (==) (==) :: (Eq a) => a -> a -> Bool注意:等式运算符,== 是一个函数。 +、*、-、/ 和 几乎所有的运营商。如果一个函数只包含特殊的 字符,默认情况下它被认为是一个中缀函数。如果我们想要 检查其类型,将其传递给另一个函数或将其作为 前缀函数,我们必须用括号括起来。
有趣。我们在这里看到了一个新事物,=> 符号。之前的一切 => 符号称为类约束。我们可以阅读前面的 像这样的类型声明:相等函数接受任意两个值 类型相同并返回 Bool。这两个的类型 values 必须是 Eq 类的成员(这是该类 约束)。
【讨论】:
这是对类型的约束,意味着a 应该是类Show 的一个实例。
例如见here
【讨论】: