-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathtypes-or-lack-thereof.html
357 lines (301 loc) · 23 KB
/
types-or-lack-thereof.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="keywords" content="Erlang, typing, dynamic, static, dialyzer, typer, strong typed, success types, reliability" />
<meta name="description" content="Erlang's type system (or lack thereof): Dynamic strong typing, A proof of reliability without static type systems, type guards and conversions, and static type inference for Erlang" />
<meta name="google-site-verification" content="mi1UCmFD_2pMLt2jsYHzi_0b6Go9xja8TGllOSoQPVU" />
<link rel="stylesheet" type="text/css" href="static/css/screen.css" media="screen" />
<link rel="stylesheet" type="text/css" href="static/css/sh/shCore.css" media="screen" />
<link rel="stylesheet" type="text/css" href="static/css/sh/shThemeLYSE2.css" media="screen" />
<link rel="stylesheet" type="text/css" href="static/css/print.css" media="print" />
<link href="rss" type="application/rss+xml" rel="alternate" title="LYSE news" />
<link rel="icon" type="image/png" href="favicon.ico" />
<link rel="apple-touch-icon" href="static/img/touch-icon-iphone.png" />
<link rel="apple-touch-icon" sizes="72x72" href="static/img/touch-icon-ipad.png" />
<link rel="apple-touch-icon" sizes="114x114" href="static/img/touch-icon-iphone4.png" />
<title>Types (or lack thereof) | Learn You Some Erlang for Great Good!</title>
</head>
<body>
<div id="wrapper">
<div id="header">
<h1>Learn you some Erlang</h1>
<span>for great good!</span>
</div> <!-- header -->
<div id="menu">
<ul>
<li><a href="content.html" title="Home">Home</a></li>
<li><a href="faq.html" title="Frequently Asked Questions">FAQ</a></li>
<li><a href="rss" title="Latest News">RSS</a></li>
<li><a href="static/erlang/learn-you-some-erlang.zip" title="Source Code">Code</a></li>
</ul>
</div><!-- menu -->
<div id="content">
<div class="noscript"><noscript>Hey there, it appears your Javascript is disabled. That's fine, the site works without it. However, you might prefer reading it with syntax highlighting, which requires Javascript!</noscript></div>
<h2>类型(或者说缺少的参考)</h2>
<h3><a class="section" name="dynamite-strong-typing">强类型</a></h3>
<p>
你也许已经注意到了当你在输入
<a class="local chapter" href="starting-out-for-real.html" title="Starting Out (for real)">真正的开始</a>这章的例子时,
还有<a class="local chapter" href="modules.html" title="Modules">模块</a>和
<a class="chapter local" href="syntax-in-functions.html" title="Syntax in Functions">函数中的语法</a>中的例子时,
我们从来没有被要求声明一个函数或变量的类型。
当使用模式匹配的时候,我们所写的代码也不知道它将匹配到什么类型。
元组<code>{<var>X</var>,<var>Y</var>}</code>可以匹配<code>{atom, 123}</code>同样也可以匹配 <code>{"A string", <<"binary stuff!">>}</code>,
<code>{2.0, ["strings","and",atoms]}</code>或者其它的任何东西。</p>
<p>当它不能工作时,错误信息会直接显示出来,但是这只会发生在你执行代码的时候。
这是因为Erlang时<em>动态类型</em>:
每个运行期发生的错误,编译器在编译模块的时候并不会告诉你,就像
<a class="chapter local" href="starting-out-for-real.html#bool-and-compare" title="Boolean Algebra & Comparison Operators">真正的开始</a>中<code>"llama + 5"</code>的例子。</p>
<img class="right" src="static/img/ham.png" width="388" height="246" alt="A knife slicing ham." title="I'll let you decide which is static and which is dynamic" />
<p>在动态和静态类型的支持者之间经常发生的争论是使用那种类型系统编写软件更安全。
一个经常被提出来的想法是,一个具有完善的静态类型系统的编译器,可以在你执行代码之前就会检查出非常多的问题。
因此,静态类型语言被认为比动态类型语言更安全。
和很多动态语言比较这也许是真的,但是Erlang却不敢苟同,当然Erlang有记录在案的记录可以证明这一点。
最好的例子是具有超过一百万行Erlang代码的<a class="external" href="http://www.erlang.se/publications/Ulf_Wiger.pdf" title="Ulf Wiger - Four-fold Increase in Productivity and Quality">Ericsson AXD 301 ATM switches</a>,
通常该系统被报告具有<em>9个9</em>(99.9999999%)的高可用性。
请注意,这并不表示,Erlang为基础的系统的中组件不是没有故障的,
但是一个交换机包括计划停机的情况下达到99.9999999%可用是非常不错的。
这部分原因是,Erlang的设计理念是,一旦一个组件出现故障,它不应当影响整个系统。
这些错误可能赖在,编程错误,硬件异常或网络失效:
而Erlang语言,包含的很多特性可以让你的程序分布式部署到不同的节点上,处理不可以预知的错误,和<em>永不</em>停机。
</p>
<p>
简而言之,绝大部分语言和类型系统的目标是减少程序的错误,Erlang采取的策略是Erlang假设任何时候任何地方都会发生错误,
并且Erlang确保覆盖所有的情况:Erlang的动态类型并不是程序的可靠性和安全行的障碍。
这听起来像在做预言一样,但是我们将在后面的章节看到我们怎么做。</p>
<div class="note">
<p><strong>注意:</strong>
选择动态类型是因为一个简单的原因;
最开始的时候,实现Erlang的人是使用动态类型的语言,
因此,Erlang使用动态类型是非常自然的选择。</p>
</div>
<p>同时Erlang是一个非常强类型的语言。一个弱类型的语言会在类型之间做自动的转换。
如果Erlang是弱类型语言,我们将在看到<code>6 = 5 + "1".</code>这种代码,但是我们在实践当中
会得到一个错误参数的异常:</p>
<pre class="brush:eshell">
1> 6 + "1".
** exception error: bad argument in an arithmetic expression
in operator +/2
called as 6 + "1"
</pre>
<p>
当然,再非常多的时候,我们想将一个类型的数据转换成另一个类型的数据:
将常规字符串变成二进制字符串去存储,或者将整形转化成浮点型。
Erlang的标准库,提供了一系列函数来完成这件事情。</p>
<h3><a class="section" name="type-conversions">类型转换</a></h3>
<p>Erlang,像其它很多语言一样,可以通过转换来改变一个数据的类型。
通常这些都是通过Erlang的内置函数来完成的,但是很多类型转换Erlang自身并没有实现。
这类函数通常使用<type>_to_<type>这种格式,并且它们都是在<code>erlang</code>这个模块内实现的。
下面是一些例子:</p>
<pre class="brush:eshell">
1> erlang:list_to_integer("54").
54
2> erlang:integer_to_list(54).
"54"
3> erlang:list_to_integer("54.32").
** exception error: bad argument
in function list_to_integer/1
called as list_to_integer("54.32")
4> erlang:list_to_float("54.32").
54.32
5> erlang:atom_to_list(true).
"true"
6> erlang:list_to_bitstring("hi there").
<<"hi there">>
7> erlang:bitstring_to_list(<<"hi there">>).
"hi there"
</pre>
<p>等等。我们正在进入一个语言中不优雅的地方:因为使用<type>_to_<type>这个模式,
每次一个新的类型被添加到语言中的时候,一堆用来进行转换的BIF将会被添加!
我们将现有的都列在了下面:</p>
<p><code>atom_to_binary/2, atom_to_list/1, binary_to_atom/2, binary_to_existing_atom/2, binary_to_list/1, bitstring_to_list/1, binary_to_term/1, float_to_list/1, fun_to_list/1, integer_to_list/1, integer_to_list/2, iolist_to_binary/1, iolist_to_atom/1, list_to_atom/1, list_to_binary/1, list_to_bitstring/1, list_to_existing_atom/1, list_to_float/1, list_to_integer/2, list_to_pid/1, list_to_tuple/1, pid_to_list/1, port_to_list/1, ref_to_list/1, term_to_binary/1, term_to_binary/2 and tuple_to_list/1.</code></p>
<p>这里有好多转换函数。
我们将在本书中看到即便不是所有类型也是绝大部分,但实际上我们并不需要使用它们全部。</p>
<h3><a class="section" name="to-guard-a-data-type">数据类型哨位</a></h3>
<p>Erlang的基本数据类型非常容易通过视觉判断出来:
元组使用大括号,列表使用中括号,字符串使用双引号,等等。
对特定的数据类型我们可以使用模式匹配:
<code>head/1</code>函数接受且只能接受列表当作参数,
因为其它数据类型使用(<code>[H|_]</code>)这个模式匹配会失败。
</p>
<img class="left" src="static/img/my-name-is.png" width="146" height="79" alt="Hi, My name is Tuple" />
<p>即便这样,我们依然无法很好的处理数字类型的值,因为我们无法控制数值的范围。
因此,我们在温度和判断驾驶年龄等函数中使用哨位。
但是我们现在又碰到了另一个障碍。我们如何写一个匹配特定的数据类型模式哨位,去匹配想数字,原子或者字符串?
</p>
<p> 有些函数专门完成此类任务。
这些函数将接受单个参数,如果参数类型是所需的则返回真,否则返回假。
它们是名字为<em>类型检测BIF</em>且是可以在哨位条件表达式中使用的函数的一部分:/p>
<pre class="brush:eshell">
is_atom/1 is_binary/1
is_bitstring/1 is_boolean/1 is_builtin/3
is_float/1 is_function/1 is_function/2
is_integer/1 is_list/1 is_number/1
is_pid/1 is_port/1 is_record/2
is_record/3 is_reference/1 is_tuple/1
</pre>
<p> 只要是允许使用哨位表达式的地方,它们都可以像其它哨位表达式那样使用。
你也许会想知道,为什么没有函数直接返回变量的数据类型(类似<code>type_of(X) -> Type</code>)。
这答案很简单。Erlang是一种针对正确场景的计算机语言:你为会发生什么,期待什么而编程。
其它情况下应尽可能快的报错。尽管这听起来很疯狂,在<a class="chapter" href="errors-and-exceptions.html">错误和异常</a>
这个章节中介绍的异常会让事情变得更清晰。在那之前,请相信我。</p>
<div class="note">
<p><strong>注意:</strong>
类型测试BIF,占哨位表达式中可使用的函数一半以上。
剩下的函数依然是BIF,但是它们并非类型测试。
它们是: <br />
<code>abs(Number), bit_size(Bitstring), byte_size(Bitstring), element(N, Tuple), float(Term), hd(List), length(List), node(), node(Pid|Ref|Port), round(Number), self(), size(Tuple|Bitstring), tl(List), trunc(Number), tuple_size(Tuple).</code></p>
<p><code>node/1</code>和<code>self/0</code>函数是关于Erlang分布式中的进程/Actor。
我们最终会使用它们,但是需要等到我们学习到相关章节。</p>
</div>
<p>
表面上来看Erlang的数据结构非常受限,
但是列表和元组可以无障碍的构造出其它非常复杂的结构。
一个例子,一个二叉树的基本节点可以用<code>{node, Value, Left, Right}</code>来表示,这里<var>Left</var>和<var>Right</var>
代表着相同的节点或者空元组。我也可以用元组来描述我自己:
</p>
<pre class="brush:erl">
{person, {name, <<"Fred T-H">>},
{qualities, ["handsome", "smart", "honest", "objective"]},
{faults, ["liar"]},
{skills, ["programming", "bass guitar", "underwater breakdancing"]}}.
</pre>
<p>
这个例子告诉我们通过嵌套元组和列表,并使用数据填充它们,
我们可以得到复杂的数据结构并且构建函数对它们进行操作。
</p>
<div class="note update">
<p><strong>一些更新:</strong><br />
Erlang的R13B04版本,我们可以看到<code>binary_to_term/2</code>这个新加入的BIF,
这个和<code>binary_to_term/1</code>函数是一样,除了第二参数是个可选的列表。
如果你传入<code>[safe]</code>,如果一个二进制包含未知原子或<a class="chapter local" href="higher-order-functions.html" title="Higher Order Functions chapter">匿名函数</a>
则这个二进制将不会解码,这将可能耗尽内存。
</p>
</div>
<h3><a class="section" name="for-type-junkies">对那些非常喜欢类型系统的人</a></h3>
<img class="right" src="static/img/type-dance.png" width="284" height="161" alt="A sign for homeless people: 'Will dance for types'" title="You know you would" />
<p>
这个章节适合对静态类型非常依赖的程序员去阅读。(This section is meant to be read by programmers who can not live without a static type system for one reason or another.)
本节包含了一些并不是所有人都能理解的一些高级理论。
我将主要介绍那些Erlang用于进行静态类型分析的工具,使用这些工具,我们可以自定义类型并且能让我们使用类型系统更加安全。
介绍这些工具,是为了能让任何人更好的理解本书后面的章节,但是不代表编写一个实用的Erlang程序,需要使用这些工具。
(These tools will be described for anyone to understand much later in the book,given that it is not necessary to use any of them to write reliable Erlang programs.)
因为稍后我们将介绍它们,我将告诉你这些工具安装,执行等的一些细节。
再次,本节主要是为了那些非常依赖高级类型系统的人所写。
(Again, this section is for those who really can't live without advanced type systems.)</p>
<p>
这些年,在Erlang构建类型系统做了很多尝试。
1997年Haskell编译器主要开发者Simon Marlow及Haskell的设计和monads理论
(<a class="external" href="http://homepages.inf.ed.ac.uk/wadler/papers/erlang/erlang.pdf" title="A Practical Subtyping System for Erlang">论文</a> 可怜的系统类型)
的贡献者Philip Wadler这两位大神,进行了一次伟大的尝试。
Joe Armstrong稍后对该论文进行了<a class="external" href="http://webcem01.cem.itesm.mx:8005/erlang/cd/downloads/hopl_erlang.pdf" title="A history of Erlang">评价</a>:</p>
<blockquote title="Joe Armstrong - A History of Erlang">
<p>某天Philip给我来电话说 a) Erlang需要一个类型系统, b) 他已经写了一个小的原型 c) 他现在要休一年的假并且他想给Erlang写一个类型系统 并且问到 “我们对这想法是否感兴趣?” 我的回答是-”感兴趣“</p>
<p>Phil Wadler和Simon Marlow花费了一年的时间来做这个类型系统并将结果发布了出来[20]。
但是这个项目的结果又带你让人失望。首先只是语言的一小部分类型可以被检查,更重要的是缺少对Erlang进程和进程间消息的检查。
</p>
</blockquote>
<p>进程和消息这两者都是Erlang的核心特性,这也许可以解释上面提到的类型系统从未被加入到Erlang的原因。
其它在Erlang中添加类型系统的尝试也都失败了。HiPE项目(一个想提升Erlang性能的项目)努力成果是开发出了Dialyzer工具,
一个具有类型推导能力的静态类型分析工具。</p>
<p>成功的类型划分才能带来完善的类型系统这个和Hindley-Milner或软件定义类型系统的原则不同。
(The type system that came out of it is based on success typings,
a concept different from Hindley-Milner or soft-typing type systems.)
成功的类型系统的原则很简单:
类型推断不会尝试找出每个表达式的确切类型,但是它会保证类型推断的正确性,
并且当类型系统报错的时候,它将告诉真正在何处出错。
(the type-inference will not try to find the exact type of every expression,
but it will guarantee that the types it infers are right,
and that the type errors it finds are really errors.)</p>
<p>最好的例子是<code>and</code>函数,
一个传入两个布尔值,并且当两个值都是真的时候返回‘true’否则返回‘fasle’。
在Haskell的类型系统中,可以这样写 <code>and :: bool -> bool -> bool</code>。
如果<code>and</code>在Erlang中实现,它将会按照下面的方式实现:</p>
<pre class="brush:erl">
and(false, _) -> false;
and(_, false) -> false;
and(true,true) -> true.
</pre>
<p>在完善的类型下,该函数的类型推导将是<code>and(_,_) -> bool()</code>,其中<var>_</var>代表‘任意值’。
这里面的原因非常简单:当执行Erlang程序的时候并且用<code>false</code>和<code>42</code>来调用这个函数,我们得到结果依然是
‘false’。在模式匹配中使用<code>_</code>是非常有价值的,当一个参数是‘fasle’的时候就可以保证函数正常工作。
如果你这样调用这个函数,ML类型的语言的类型系统会抛出一个适配异常(用户内心的痛呀)。但是Erlang不会。
如果你想了解更多这方面的知识,你可以去阅读下 <a class="external" href="http://www.it.uu.se/research/group/hipe/papers/succ_types.pdf" title="Tobias Lindahl & Constantinos Sagonas - Practical Type Inference Based on Success Typings">实现完善的类型</a>,
这将会解释这种行为后面的关系。我非常鼓励那种缺了类型就很难受的人们去阅读下,
它确实非常有趣也非常有实践意义的。
</p>
<p>在Erlang增强提案中(<a class="external" href="http://www.erlang.org/eeps/eep-0008.html" title="EEP 8">EEP 8</a>)详细的描述了类型定义和函数注解。
如果你对Erlang中完善的类型感兴趣的话,你可以查看下<a class="external" href="http://user.it.uu.se/~tobiasl/publications/typer.pdf" title="TypEr: A Type Annotator of Erlang Code">TypEr application</a>
和Dialyzer,在Erlang的常规发行版中都能找到这两个工具。
你可以通过输入<code>$ typer --help</code> 和<code>$ dialyzer --help</code>来使用它们(
在Windows上,如果在你的当前目录中可以看到这两个程序,你可以通过输入<code>typer.exe --help</code> 和 <code>dialyzer.exe --help</code>
来使用它们)。</p>
<p>
TypEr可以为函数生成类型注解。
我们将在这个非常小的 <a class="source" href="static/erlang/fifo.erl" title="A Queue module" >FIFO实现</a>上使用它,
它会生成下面的类型注解:</p>
<pre class="brush:erl">
%% File: fifo.erl
%% --------------
-spec new() -> {'fifo',[],[]}.
-spec push({'fifo',_,_},_) -> {'fifo',nonempty_maybe_improper_list(),_}.
-spec pop({'fifo',_,maybe_improper_list()}) -> {_,{'fifo',_,_}}.
-spec empty({'fifo',_,_}) -> bool().
</pre>
<img class="right explanation" src="static/img/fifo.png" width="237" height="162" alt="Implementation of fifo (queues): made out of two stacks (last-in first-out)." title="Implementation of fifo (queues): made out of two stacks (last-in first-out)." />
<p>这是非常正确的。因为<code>lists:reverse/1</code>不支持非常规列表,所以非常规列表是应当避免的,
但是如果某人会绕过模块的接口直接放入一个非常规列表(Improper lists 不知道该如何翻译为好,感觉这概念完全是来自于LISP)。
在这种情况下,<code>push/2</code>和<code>pop/2</code>这两个函数依然会成功,直到某些特定条件被触发。
这告诉我们,要么加上哨位做限制要么手工优化我们的类型定义。
假设我们改称<code>-spec push({fifo,list(),list()},_) -> {fifo,nonempty_list(),list()}.</code>
并且一个函数给这个模块的<code>push/2</code>函数传入非常规列表:
当我们用Dialyzer(一个检查和匹配类型的工具)扫描这个温家你的时候,我们会看到这样的错误消息
<samp>"The call fifo:push({fifo,[1|2],[]},3) breaks the contract '<Type definition here>'</samp>。</p>
<p>当某些代码出现异常的时候,Dialyzer就会向你各种抱怨,并且一旦它真的开始抱怨了,它一般是正确的(
Dialyzer会在很多情况下输出错误纤细,如分支永远不会使用或函数定义和实现不同等)。
多态数据类型一样可以使用Dialyzer来分析:
尽管Erlang的程序员很少使用以下写法,但是我们依然可以将<code>hd()</code>函数的注解写成<code>-spec([A]) -> A.</code>,
并且它能被Dialyzer正确分析。</p>
<div class="note koolaid">
<p><strong>不要盲从:</strong><br />
你不要期望Dialyzer和TypEr能处理好所有类型,一些东西,例如带有构造函数的类型,一阶类型和类型递归。
Erlang的类型系统只是注解,只有在你希望的时候,否则对实际的编译是没有任何影响和限制。
当类型系统检查了你的代码并且认为没有问题的时候,
它没办法告诉你当你代码执行了(即便你可能碰到一些错误的代码能正确运行。。。),
这些代码是否正确(或能稳定的执行2年或更久)。</p>
<p> 尽管类型递归非常有趣,但是当前TypEr和Dialyzer并没有包含(上面的论文解释了原因)。
眼下,通过定义自己的类型来模拟类型递归可能是最好的选择。</p>
<p> 当然Erlang的类型系统并不像Scala,Haskell或OCaml那样全面。
并且它的警告和错误信息有点像密码一样不那么用户友好。
如果你不能忍受动态类型或获得更多的安全,这是一种不错的妥协;
只把它当作一个工具,不要过度依赖它。</p>
</div>
<div class="note update">
<p><strong>更新</strong><br />
从R13B04版本之后,Dialyzer中类型递归被认为是一个实验特性。
这让前面的<em>不要盲从</em>部分是错误的。</p>
<p>注意<a class="docs" href="http://erlang.org/doc/reference_manual/typespec.html" title="Official Types and Functions Specifications spec">类型文档</a>也被官方化了(即便还在不断变更),
但这已经比EEP8更完善了。</p>
</div>
<ul class="navigation">
<li><a href="syntax-in-functions.html" title="Previous chapter">< Previous</a></li>
<li><a href="contents.html" title="Index">Index</a></li>
<li><a href="recursion.html" title="Next chapter">Next ></a></li>
</ul>
</div><!-- content -->
<div id="footer">
<a href="http://creativecommons.org/licenses/by-nc-nd/3.0/" title="Creative Commons License Details"><img src="static/img/cc.png" width="88" height="31" alt="Creative Commons Attribution Non-Commercial No Derivative License" /></a>
<p>Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution Non-Commercial No Derivative License</p>
</div> <!-- footer -->
</div> <!-- wrapper -->
<div id="grass" />
<script type="text/javascript" src="static/js/shCore.js"></script>
<script type="text/javascript" src="static/js/shBrushErlang2.js%3F11"></script>
<script type="text/javascript">
SyntaxHighlighter.defaults.gutter = false;
SyntaxHighlighter.all();
</script>
</body>
</html>