阅读类型
在本指南的 核心语言 部分,我们已经通过一堆交互式示例对该语言形成了一定的认识。那么,我们打算再次进行这样的学习,但这次我们有一个新的问题。该值是什么类型?
基元和列表
让我们输入一些简单的表达式,看看会发生什么
单击这个黑框 ⬆️ 光标应该会开始闪烁。输入 3.1415
并按 ENTER 键。它应该会打印出 3.1415
,后跟类型 Float
。
好的,但这里到底发生了什么?每条记录都显示了一个值,以及该值碰巧是什么类型。你可以这样大声朗读这些示例
- 值
"hello"
是一个String
。 - 值
False
是一个Bool
。 - 值
3
是一个Int
。 - 值
3.1415
是一个Float
。
Elm 能够弄清楚你输入的任何值的类型!让我们看看列表会发生什么
你可以将这些类型读作
- 我们有一个用
String
值填充的List
。 - 我们有一个用
Float
值填充的List
。
类型是对我们正在查看的特定值的大致描述。
函数
我们看看一些函数的类型
尝试输入 round
或 sqrt
来查看其他一些函数类型 ⬆️
String.length
函数的类型为 String -> Int
。这意味着它必须采用 String
参数,并且肯定会返回一个 Int
值。因此,让我们尝试为它提供一个参数
因此,我们从一个 String -> Int
函数开始,并为它提供一个 String
参数。这会产生 Int
。
但是,当您不提供 String
时会发生什么?尝试输入 String.length [1,2,3]
或 String.length True
以查看会发生什么 ⬆️
您将发现 String -> Int
函数必须获取 String
参数!
注意:采用多个参数的函数最终会有越来越多的箭头。例如,这是一个采用两个参数的函数
[ { "input": "String.repeat", "value": "\u001b[36m<function>\u001b[0m", "type_": "Int -> String -> String" } ]提供两个参数,如
String.repeat 3 "ha"
,将会产生"hahaha"
。将->
视为一种分离参数的奇怪方式很有用,但我解释了 此处 的真正原因。这相当简洁!
类型注释
到目前为止,我们只是让 Elm 找出类型,但它还允许您在定义上方的行中编写一个类型注释。因此,在编写代码时,您可以说这样的话
half : Float -> Float
half n =
n / 2
-- half 256 == 128
-- half "3" -- error!
hypotenuse : Float -> Float -> Float
hypotenuse a b =
sqrt (a^2 + b^2)
-- hypotenuse 3 4 == 5
-- hypotenuse 5 12 == 13
checkPower : Int -> String
checkPower powerLevel =
if powerLevel > 9000 then "It's over 9000!!!" else "Meh"
-- checkPower 9001 == "It's over 9000!!!"
-- checkPower True -- error!
添加类型注释并不是必需的,但绝对推荐!优点包括
- 错误信息质量 — 当您添加类型注释时,它告诉编译器您尝试做什么。您的实现可能存在错误,现在编译器可以根据您声明的意图进行比较。“您说参数
powerLevel
是一个Int
,但它被用作一个String
!” - 文档 — 当您稍后重新审阅代码(或当同事第一次审阅它时),确切地知道函数的输入和输出将非常有帮助,而不必非常仔细地阅读该实现。
不过,人们在类型注释中可能会犯错误,所以如果注释与实现不匹配,会发生什么情况?编译器会自行找出所有类型,并检查您的注释是否与实际答案匹配。换句话说,编译器始终会验证您添加的所有注释是否正确。因此,您会获得更好的错误消息,而且文档始终保持最新!
类型变量
在浏览更多 Elm 代码时,您将开始看到类型注释中包含小写字母。一个常见的示例是 List.length
函数
注意到类型中的小写a
吗?它称为类型变量。它可根据List.length
的使用方式而变化
我们只需要长度即可,因此列表中存放什么并不重要。因此,类型变量a
表示我们可以匹配任何类型。我们来看看另一个常见的示例
同样,类型变量a
可根据List.reverse
的使用方式而变化。但在本例中,我们有一个a
在参数和结果中。这意味着如果你给出一个List Int
,你也必须得到一个List Int
。一旦我们决定了a
是什么,那么它在任何地方都是这个。
注意:类型变量必须以小写字母开头,但它们可以是完整的单词。我们可以将
List.length
的类型写为List value -> Int
,并且我们可以将List.reverse
的类型写为List element -> List element
。只要它们以小写字母开头即可。根据惯例,很多地方都使用了类型变量a
和b
,但是一些类型注释受益于更具体的名称。
受限类型变量
Elm 中有一种称为受限类型变量的特殊类型的类型变量。最常见的示例是number
类型。negate
函数使用它
尝试高于的表达,例如 negate 3.1415
和 negate (round 3.1415)
和 negate "hi"
⬆️
通常,类型变量可以用任何东西填充,但number
只能由Int
和Float
值填充。它限制了可能性。
受限类型变量的完整列表是
number
允许Int
和Float
appendable
允许String
和List a
comparable
允许Int
、Float
、Char
、String
、comparable
值列表/元组compappend
允许String
和List comparable
这些受约束的类型变量的存在是为了使得像 (+)
和 (<)
的操作符更加灵活。
现在我们已经相当透彻地介绍了值和函数的类型,但是当我们开始想要更复杂的数据结构的时候,这些内容看起来会是什么样子呢?