1
1
16-协议
2
2
========
3
- [ 协议和结构体] ( #161-%E5%8D%8F%E8%AE%AE%E5%92%8C%E7%BB%93%E6%9E%84%E4%BD%93 ) < br />
4
- [ 回归大众 ] ( #162-%E5%9B%9E%E5%BD%92%E5%A4%A7%E4%BC%97 ) < br />
5
- [ 内建协议] ( #163-%E5%86%85%E5%BB%BA%E5%8D%8F%E8%AE%AE ) < br />
3
+ [ 协议和结构体] ( #161-%E5%8D%8F%E8%AE%AE%E5%92%8C%E7%BB%93%E6%9E%84%E4%BD%93 )
4
+ [ 回归一般化 ] ( # )
5
+ [ 内建协议] ( #163-%E5%86%85%E5%BB%BA%E5%8D%8F%E8%AE%AE )
6
6
7
7
协议是实现Elixir多态性的重要机制。任何数据类型只要实现了某协议,那么该协议的分发就是可用的。
8
8
让我们看个例子。
9
9
10
- > 这里的协议二字对于熟悉ruby等具有ducktyping特性的语言的人来说会比较容易理解 。
10
+ > 这里的“协议”二字对于熟悉ruby等具有duck-typing特性的语言的人来说会比较容易理解 。
11
11
12
- 在Elixir中,只有false和nil被认为是false。其它的值都被认为是true。
13
- 根据程序需要,有时需要一个``` blank? ``` 协议,返回一个布尔值,以说明该参数是否为空。
12
+ 在Elixir中,只有false和nil被认为是false的。其它的值都被认为是true。
13
+ 根据程序需要,有时需要一个``` blank? ``` 协议(注意,我们此处称之为“协议”),
14
+ 返回一个布尔值,以说明该参数是否为空。
14
15
举例来说,一个空列表或者空二进制可以被认为是空的。
15
16
16
17
我们可以如下定义协议:
17
- ```
18
+ ``` elixir
18
19
defprotocol Blank do
19
20
@doc "Returns true if data is considered blank/empty"
20
21
def blank? (data)
21
22
end
22
23
```
23
24
24
- 这个协议期待一个函数``` blank? ``` ,它接受一个待实现的参数。
25
- 我们为不同的数据类型实现这个协议:
26
- ```
27
- # Integers are never blank
25
+ 从上面代码的语法上看,这个协议```Blank ```声明了一个函数```blank?```,接受一个参数。
26
+ 看起来这个“协议”像是一份声明,需要后续的实现。
27
+ 下面我们为不同的数据类型实现这个协议:
28
+ ```elixir
29
+ # 整型永远不为空
28
30
defimpl Blank , for: Integer do
29
31
def blank? (_ ), do: false
30
32
end
31
33
32
- # Just empty list is blank
34
+ # 只有空列表是“空”的
33
35
defimpl Blank , for: List do
34
36
def blank? ([]), do: true
35
37
def blank? (_ ), do: false
36
38
end
37
39
38
- # Just empty map is blank
40
+ # 只有空map是“空”
39
41
defimpl Blank , for: Map do
40
- # Keep in mind we could not pattern match on %{} because
41
- # it matches on all maps. We can however check if the size
42
- # is zero (and size is a fast operation).
42
+ # 一定要记住,我们不能匹配 %{} ,因为它能match所有的map。
43
+ # 但是我们能检查它的size是不是0
44
+ # 检查size是很快速的操作
43
45
def blank? (map), do: map_size (map) == 0
44
46
end
45
47
46
- # Just the atoms false and nil are blank
48
+ # 只有false和nil这两个原子被认为是空得
47
49
defimpl Blank , for: Atom do
48
50
def blank? (false ), do: true
49
51
def blank? (nil ), do: true
65
67
- 元祖
66
68
67
69
现在手边有了一个定义并被实现的协议,如此使用之:
68
- ```
70
+ ``` elixir
69
71
iex> Blank .blank? (0 )
70
72
false
71
73
iex> Blank .blank? ([])
@@ -75,66 +77,69 @@ false
75
77
```
76
78
77
79
给它传递一个并没有实现该协议的数据类型,会导致报错:
78
- ```
80
+ ``` elixir
79
81
iex> Blank .blank? (" hello" )
80
82
** (Protocol .UndefinedError ) protocol Blank not implemented for " hello"
81
83
```
82
84
83
85
## 16.1-协议和结构体
84
- 协议和结构体一起使用能够大大加强Elixir的可扩展性。< br />
86
+ 协议和结构体一起使用能够加强Elixir的可扩展性。
85
87
86
- 在前面几章中我们知道,尽管结构体就是图 ,但是它们和图并不共享各自协议的实现。
88
+ 在前面几章中我们知道,尽管结构体本质上就是图(map) ,但是它们和图并不共享各自协议的实现。
87
89
像前几章一样,我们先定义一个名为``` User ``` 的结构体:
88
- ```
90
+ ``` elixir
89
91
iex> defmodule User do
90
92
.. .> defstruct name: " john" , age: 27
91
93
.. .> end
92
- {:module, User,
93
- <<70, 79, 82, ...>>, {:__struct__, 0}}
94
- ```
95
- 然后看看:
96
- ```
94
+ {:module , User , << 70 , 79 , 82 , .. .>> , {:__struct__ , 0 }}
95
+ ```
96
+ 然后看看能不能用刚才定义的协议:
97
+ ``` elixir
97
98
iex> Blank .blank? (%{})
98
99
true
99
100
iex> Blank .blank? (%User {})
100
101
** (Protocol .UndefinedError ) protocol Blank not implemented for %User {age: 27 , name: " john" }
101
102
```
102
103
103
- 结构体没有使用协议针对图的实现,而是使用它自己的协议实现:
104
- ```
104
+ 果然,结构体没有使用协议针对图的实现。
105
+ 因此,结构体需要使用它自己的协议实现:
106
+ ``` elixir
105
107
defimpl Blank , for: User do
106
108
def blank? (_ ), do: false
107
109
end
108
110
```
109
111
110
- 如果愿意,你可以定义你自己的语法来检查一个user为不为空。
111
- 不光如此,你还可以使用结构体创建更强健的数据类型,比如队列,然后实现所有相关的协议,比如枚举(Enumerable)或检查是否为空。
112
+ 如果愿意,你可以定义你自己的语法来检查一个user是否为空。
113
+ 不光如此,你还可以使用结构体创建更强健的数据类型(比如队列),然后实现所有相关的协议
114
+ (就像枚举``` Enumerable ``` 那样),检查是否为空等等。
112
115
113
- 有些时候,程序员们希望给结构体提供某些默认的协议实现。 因为显式给所有结构体都实现某些协议实在是太枯燥了。
114
- 这引出了下一节“回归大众 ”(falling back to any)的说法。
116
+ 有些时候,程序员们希望给结构体提供某些默认的协议实现, 因为显式给所有结构体都实现某些协议实在是太枯燥了。
117
+ 这引出了下一节“回归一般化 ”(falling back to any)的说法。
115
118
116
- ## 16.2-回归大众
117
- 能够给所有类型提供默认的协议实现肯定是很方便的。在定义协议时,把``` @fallback_to_any ``` 设置为``` true ``` 即可:
118
- ```
119
+ ## 16.2-回归一般化
120
+ 能够给所有类型提供默认的协议实现肯定是很方便的。
121
+ 在定义协议时,把``` @fallback_to_any ``` 设置为``` true ``` 即可:
122
+ ``` elixir
119
123
defprotocol Blank do
120
124
@fallback_to_any true
121
125
def blank? (data)
122
126
end
123
127
```
124
128
现在这个协议可以被这么实现:
125
- ```
129
+ ```elixir
126
130
defimpl Blank , for: Any do
127
131
def blank? (_ ), do: false
128
132
end
129
133
```
130
134
131
- 现在,那些我们还没有实现``` Blank ``` 协议的数据类型(包括结构体)也可以来判断是否为空了。
135
+ 现在,那些我们还没有实现``` Blank ``` 协议的数据类型(包括结构体)也可以来判断是否为空了
136
+ (虽然默认会被认为是false,哈哈)。
132
137
133
138
## 16.3-内建协议
134
- Elixir自带了一些内建协议 。在前面几章中我们讨论过枚举模块,它提供了许多方法。
139
+ Elixir自带了一些内建的协议 。在前面几章中我们讨论过枚举模块,它提供了许多方法。
135
140
只要任何一种数据结构它实现了Enumerable协议,就能使用这些方法:
136
141
137
- ```
142
+ ``` elixir
138
143
iex> Enum .map [1 , 2 , 3 ], fn (x) - > x * 2 end
139
144
[2 ,4 ,6 ]
140
145
iex> Enum .reduce 1 .. 3 , 0 , fn (x, acc) - > x + acc end
@@ -143,60 +148,47 @@ iex> Enum.reduce 1..3, 0, fn(x, acc) -> x + acc end
143
148
144
149
另一个例子是``` String.Chars ``` 协议,它规定了如何将包含字符的数据结构转换为字符串类型。
145
150
它暴露为函数``` to_string ``` :
146
- ```
151
+ ``` elixir
147
152
iex> to_string :hello
148
153
" hello"
149
154
```
150
155
151
- 注意,在Elixir中,字符串插值操作调用的是 ``` to_string ``` 函数:
152
- ```
156
+ 注意,在Elixir中,字符串插值操作里面调用了 ``` to_string ``` 函数:
157
+ ``` elixir
153
158
iex> " age: #{ 25 } "
154
159
" age: 25"
155
160
```
156
- 上面代码能工作是因为数字类型实现了``` String.Chars ``` 协议。如果传进去的是元组就会报错:
157
- ```
161
+ 上面代码能工作,是因为25是数字类型,而数字类型实现了``` String.Chars ``` 协议。
162
+ 如果传进去的是元组就会报错:
163
+ ``` elixir
158
164
iex> tuple = {1 , 2 , 3 }
159
165
{1 , 2 , 3 }
160
166
iex> " tuple: #{ tuple } "
161
167
** (Protocol .UndefinedError ) protocol String .Chars not implemented for {1 , 2 , 3 }
162
168
```
163
169
164
170
当想要打印一个比较复杂的数据结构时,可以使用``` inspect ``` 函数。该函数基于协议``` Inspect ``` :
165
- ```
171
+ ``` elixir
166
172
iex> " tuple: #{ inspect tuple } "
167
173
" tuple: {1, 2, 3}"
168
174
```
169
175
170
- _ Inspect_协议用来将任意数据类型转换为可读的文字表述 。IEx用来打印表达式结果用的就是它:
171
- ```
176
+ _ Inspect _ 协议用来将任意数据类型转换为可读的文字表述 。IEx用来打印表达式结果用的就是它:
177
+ ``` elixir
172
178
iex> {1 , 2 , 3 }
173
179
{1 ,2 ,3 }
174
180
iex> %User {}
175
181
%User {name: " john" , age: 27 }
176
182
```
177
183
178
- 记住,习惯上来说,无论何时,头顶#号被插的值,会被表现成一个不合语法的字符串。
184
+ > ``` inspect ``` 是ruby中非常常用的方法。
185
+ 这也能看出Elixir的作者们真是绞尽脑汁把Elixir的语法尽量往ruby上靠。
186
+
187
+ 记住,头顶着#号被插的值,会被``` to_string ``` 表现成纯字符串。
179
188
在转换为可读的字符串时丢失了信息,因此别指望还能从该字符串取回原来的那个对象:
180
- ```
189
+ ``` elixir
181
190
iex> inspect & (&1 + 2 )
182
191
" #Function<6.71889879/1 in :erl_eval.expr/5>"
183
192
```
184
193
185
194
Elixir中还有些其它协议,但本章就讲这几个比较常用的。下一章将讲讲Elixir中的错误捕捉以及异常。
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
0 commit comments