Skip to content

Commit d6668fd

Browse files
committed
general improvement
1 parent c343388 commit d6668fd

File tree

1 file changed

+59
-67
lines changed

1 file changed

+59
-67
lines changed

16-proto.md

Lines changed: 59 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,51 @@
11
16-协议
22
========
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)
66

77
协议是实现Elixir多态性的重要机制。任何数据类型只要实现了某协议,那么该协议的分发就是可用的。
88
让我们看个例子。
99

10-
>这里的协议二字对于熟悉ruby等具有ducktyping特性的语言的人来说会比较容易理解
10+
>这里的“协议”二字对于熟悉ruby等具有duck-typing特性的语言的人来说会比较容易理解
1111
12-
在Elixir中,只有false和nil被认为是false。其它的值都被认为是true。
13-
根据程序需要,有时需要一个```blank?```协议,返回一个布尔值,以说明该参数是否为空。
12+
在Elixir中,只有false和nil被认为是false的。其它的值都被认为是true。
13+
根据程序需要,有时需要一个```blank?```协议(注意,我们此处称之为“协议”),
14+
返回一个布尔值,以说明该参数是否为空。
1415
举例来说,一个空列表或者空二进制可以被认为是空的。
1516

1617
我们可以如下定义协议:
17-
```
18+
```elixir
1819
defprotocol Blank do
1920
@doc "Returns true if data is considered blank/empty"
2021
def blank?(data)
2122
end
2223
```
2324

24-
这个协议期待一个函数```blank?```,它接受一个待实现的参数。
25-
我们为不同的数据类型实现这个协议:
26-
```
27-
# Integers are never blank
25+
从上面代码的语法上看,这个协议```Blank```声明了一个函数```blank?```,接受一个参数。
26+
看起来这个“协议”像是一份声明,需要后续的实现。
27+
下面我们为不同的数据类型实现这个协议:
28+
```elixir
29+
# 整型永远不为空
2830
defimpl Blank, for: Integer do
2931
def blank?(_), do: false
3032
end
3133

32-
# Just empty list is blank
34+
# 只有空列表是“空”的
3335
defimpl Blank, for: List do
3436
def blank?([]), do: true
3537
def blank?(_), do: false
3638
end
3739

38-
# Just empty map is blank
40+
# 只有空map是“空”
3941
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是很快速的操作
4345
def blank?(map), do: map_size(map) == 0
4446
end
4547

46-
# Just the atoms false and nil are blank
48+
# 只有false和nil这两个原子被认为是空得
4749
defimpl Blank, for: Atom do
4850
def blank?(false), do: true
4951
def blank?(nil), do: true
@@ -65,7 +67,7 @@ end
6567
- 元祖
6668

6769
现在手边有了一个定义并被实现的协议,如此使用之:
68-
```
70+
```elixir
6971
iex> Blank.blank?(0)
7072
false
7173
iex> Blank.blank?([])
@@ -75,66 +77,69 @@ false
7577
```
7678

7779
给它传递一个并没有实现该协议的数据类型,会导致报错:
78-
```
80+
```elixir
7981
iex> Blank.blank?("hello")
8082
** (Protocol.UndefinedError) protocol Blank not implemented for "hello"
8183
```
8284

8385
## 16.1-协议和结构体
84-
协议和结构体一起使用能够大大加强Elixir的可扩展性。<br/>
86+
协议和结构体一起使用能够加强Elixir的可扩展性。
8587

86-
在前面几章中我们知道,尽管结构体就是图,但是它们和图并不共享各自协议的实现。
88+
在前面几章中我们知道,尽管结构体本质上就是图(map),但是它们和图并不共享各自协议的实现。
8789
像前几章一样,我们先定义一个名为```User```的结构体:
88-
```
90+
```elixir
8991
iex> defmodule User do
9092
...> defstruct name: "john", age: 27
9193
...> 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
9798
iex> Blank.blank?(%{})
9899
true
99100
iex> Blank.blank?(%User{})
100101
** (Protocol.UndefinedError) protocol Blank not implemented for %User{age: 27, name: "john"}
101102
```
102103

103-
结构体没有使用协议针对图的实现,而是使用它自己的协议实现:
104-
```
104+
果然,结构体没有使用协议针对图的实现。
105+
因此,结构体需要使用它自己的协议实现:
106+
```elixir
105107
defimpl Blank, for: User do
106108
def blank?(_), do: false
107109
end
108110
```
109111

110-
如果愿意,你可以定义你自己的语法来检查一个user为不为空。
111-
不光如此,你还可以使用结构体创建更强健的数据类型,比如队列,然后实现所有相关的协议,比如枚举(Enumerable)或检查是否为空。
112+
如果愿意,你可以定义你自己的语法来检查一个user是否为空。
113+
不光如此,你还可以使用结构体创建更强健的数据类型(比如队列),然后实现所有相关的协议
114+
(就像枚举```Enumerable```那样),检查是否为空等等。
112115

113-
有些时候,程序员们希望给结构体提供某些默认的协议实现因为显式给所有结构体都实现某些协议实在是太枯燥了。
114-
这引出了下一节“回归大众”(falling back to any)的说法。
116+
有些时候,程序员们希望给结构体提供某些默认的协议实现因为显式给所有结构体都实现某些协议实在是太枯燥了。
117+
这引出了下一节“回归一般化”(falling back to any)的说法。
115118

116-
## 16.2-回归大众
117-
能够给所有类型提供默认的协议实现肯定是很方便的。在定义协议时,把```@fallback_to_any```设置为```true```即可:
118-
```
119+
## 16.2-回归一般化
120+
能够给所有类型提供默认的协议实现肯定是很方便的。
121+
在定义协议时,把```@fallback_to_any```设置为```true```即可:
122+
```elixir
119123
defprotocol Blank do
120124
@fallback_to_any true
121125
def blank?(data)
122126
end
123127
```
124128
现在这个协议可以被这么实现:
125-
```
129+
```elixir
126130
defimpl Blank, for: Any do
127131
def blank?(_), do: false
128132
end
129133
```
130134

131-
现在,那些我们还没有实现```Blank```协议的数据类型(包括结构体)也可以来判断是否为空了。
135+
现在,那些我们还没有实现```Blank```协议的数据类型(包括结构体)也可以来判断是否为空了
136+
(虽然默认会被认为是false,哈哈)。
132137

133138
## 16.3-内建协议
134-
Elixir自带了一些内建协议。在前面几章中我们讨论过枚举模块,它提供了许多方法。
139+
Elixir自带了一些内建的协议。在前面几章中我们讨论过枚举模块,它提供了许多方法。
135140
只要任何一种数据结构它实现了Enumerable协议,就能使用这些方法:
136141

137-
```
142+
```elixir
138143
iex> Enum.map [1, 2, 3], fn(x) -> x * 2 end
139144
[2,4,6]
140145
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
143148

144149
另一个例子是```String.Chars```协议,它规定了如何将包含字符的数据结构转换为字符串类型。
145150
它暴露为函数```to_string```
146-
```
151+
```elixir
147152
iex> to_string :hello
148153
"hello"
149154
```
150155

151-
注意,在Elixir中,字符串插值操作调用的是```to_string```函数:
152-
```
156+
注意,在Elixir中,字符串插值操作里面调用了```to_string```函数:
157+
```elixir
153158
iex> "age: #{25}"
154159
"age: 25"
155160
```
156-
上面代码能工作是因为数字类型实现了```String.Chars```协议。如果传进去的是元组就会报错:
157-
```
161+
上面代码能工作,是因为25是数字类型,而数字类型实现了```String.Chars```协议。
162+
如果传进去的是元组就会报错:
163+
```elixir
158164
iex> tuple = {1, 2, 3}
159165
{1, 2, 3}
160166
iex> "tuple: #{tuple}"
161167
** (Protocol.UndefinedError) protocol String.Chars not implemented for {1, 2, 3}
162168
```
163169

164170
当想要打印一个比较复杂的数据结构时,可以使用```inspect```函数。该函数基于协议```Inspect```
165-
```
171+
```elixir
166172
iex> "tuple: #{inspect tuple}"
167173
"tuple: {1, 2, 3}"
168174
```
169175

170-
_Inspect_协议用来将任意数据类型转换为可读的文字表述。IEx用来打印表达式结果用的就是它:
171-
```
176+
_Inspect_ 协议用来将任意数据类型转换为可读的文字表述。IEx用来打印表达式结果用的就是它:
177+
```elixir
172178
iex> {1, 2, 3}
173179
{1,2,3}
174180
iex> %User{}
175181
%User{name: "john", age: 27}
176182
```
177183

178-
记住,习惯上来说,无论何时,头顶#号被插的值,会被表现成一个不合语法的字符串。
184+
>```inspect```是ruby中非常常用的方法。
185+
这也能看出Elixir的作者们真是绞尽脑汁把Elixir的语法尽量往ruby上靠。
186+
187+
记住,头顶着#号被插的值,会被```to_string```表现成纯字符串。
179188
在转换为可读的字符串时丢失了信息,因此别指望还能从该字符串取回原来的那个对象:
180-
```
189+
```elixir
181190
iex> inspect &(&1+2)
182191
"#Function<6.71889879/1 in :erl_eval.expr/5>"
183192
```
184193

185194
Elixir中还有些其它协议,但本章就讲这几个比较常用的。下一章将讲讲Elixir中的错误捕捉以及异常。
186-
187-
188-
189-
190-
191-
192-
193-
194-
195-
196-
197-
198-
199-
200-
201-
202-

0 commit comments

Comments
 (0)