├── meta ├── Chapter1.md ├── Chapter2.md └── Chapter3.md ├── VERSION ├── CONTRIBUTORS.md ├── getting_started ├── Chapter20.md ├── Chapter15.md ├── Chapter3.md ├── Chapter18.md ├── Chapter9.md ├── Chapter1.md ├── Chapter11.md ├── Chapter10.md ├── Chapter4.md ├── Chapter13.md ├── Chapter19.md ├── Chapter14.md ├── Chapter6.md ├── Chapter17.md ├── Chapter16.md ├── Chapter7.md ├── Chapter12.md ├── Chapter8.md ├── Chapter5.md └── Chapter2.md ├── README.md ├── exunit └── Chapter1.md └── mix ├── Chapter3.md ├── Chapter1.md └── Chapter2.md /meta/Chapter1.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /meta/Chapter2.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /meta/Chapter3.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.13-dev 2 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | ## Contributors 2 | 3 | [lidåshuang](https://github.com/lidashuang) (ldshuang@gmail.com) 4 | 5 | [Thomas Lee](https://github.com/taomaree) (taomaree@gmail.com) 6 | -------------------------------------------------------------------------------- /getting_started/Chapter20.md: -------------------------------------------------------------------------------- 1 | # 20 下一步 2 | 3 | 渴望学习更多?继续阅读! 4 | 5 | # 20.1 编写你的第一个Elixir项目 6 | 7 | 为了帮助你开始第一个项目,Elixir默认提供了了编译工具[`Mix`](http://elixir-lang.org/getting_started/mix/1.html)。你能用下面的命令得到一个新项目的模板: 8 | 9 | ``` 10 | mix new path/to/new/project 11 | ``` 12 | 13 | 你通过下面的链接可以学习更多有关Mix的其他Elixir内置的应用的内容: 14 | 15 | * [Mix - a build tool for Elixir](http://elixir-lang.org/getting_started/mix/1.html) 16 | * [ExUnit - a unit test framework](http://elixir-lang.org/getting_started/ex_unit/1.html) 17 | 18 | ## 20.2 社区和其他资源 19 | 20 | 在旁边,你能找到一些Elixir图书和播客的链接。其中包含了大量的Elixir资源,像会议座谈,开源项目,和其他社区生产的学习材料。 21 | 22 | 记住,如果你遇到任何困难,你总能访问**irc.freenode.net**上的**#elixir**频道或发送邮件到[邮件列表](http://groups.google.com/group/elixir-lang-talk)。我们保证总有人愿意帮助你。如果你愿意追踪最新的新闻和声明,请经常访问关注[博客](http://elixir-lang.org/blog/)和[elixir-core邮件列表](http://groups.google.com/group/elixir-lang-core)上的语言发展。 23 | 24 | 别忘了你也能直接查看[Elixir的源代码](https://github.com/elixir-lang/elixir),大部分的源代码都是用Elixir写成的(主要位于`lib`文件夹下),或探索[Eliixr的文档](http://elixir-lang.org/docs.html)。 25 | 26 | # 20.3 Erlang 27 | 28 | 在这个语言官网的主页,有这么一句话: 29 | 30 | > Elixir is a programming language built on top of the Erlang VM. 31 | 32 | 或早或晚,一个Elixir开发者将会遇到现存的Erlang代码库。这里是一些在线的资源涵盖了Erlang的基本和一些高级的特性: 33 | 34 | * [The Erlang Syntax: A Crash Course](http://elixir-lang.org/crash-course.html)提供了一个对Erlang语法的精简的介绍。每一个代码片段都和对应的Elixir代码做比较。这是一个可以让你接触一些Erlang的语法,但同时也回顾一些你在这个系列教程中所学到的东西的机会。 35 | 36 | * Erlang的官方网站有一个带有图片的简短的[教程](http://www.erlang.org/course/concurrent_programming.html),它简略地描述了Erlang的并发编程。 37 | 38 | * [Learn Your Some Erlang Code for Great Good!](http://learnyousomeerlang.com/)是一个对Erlang的非常好的介绍,它的设计宗旨,标准库,最佳实践,以及更多。如果你对Elixir是认真的,你将会需要对Erlang的深入理解。一旦你通读了上面提到的入门基础,你可以轻松地跳过本书有关语法的的前面几章。当你到了[The Hitchhiker's Guide to Concurrenty](http://learnyousomeerlang.com/the-hitchhikers-guide-to-concurrency)这一章,真正有趣的事情才开始了。 39 | -------------------------------------------------------------------------------- /getting_started/Chapter15.md: -------------------------------------------------------------------------------- 1 | # 15 Structs 2 | 3 | 在早期的几章中,我们已经学习了表单: 4 | 5 | ``` 6 | iex> map = %{a: 1, b: 2} 7 | %{a: 1, b: 2} 8 | iex> map[:a] 9 | 1 10 | iex> %{map | a: 3} 11 | %{a: 3, b: 2} 12 | ``` 13 | 14 | Structs是在表单上的扩展,带来了默认值,编译时保证,和Elixir中的多态。 15 | 16 | 定义一个struct,我们只需要去调用在一个模块内调用`defstruct/1` 17 | 18 | ``` 19 | iex> defmodule User do 20 | ...> defstruct name: "jose", age: 27 21 | ...> end 22 | {:module, User, 23 | <<70, 79, 82, ...>>, {:__struct__, 0}} 24 | ``` 25 | 26 | 现在我们用语法`%User{}`来能创建这个stuct的一个“实例”: 27 | 28 | ``` 29 | iex> %User{} 30 | %User{name: "jose", age: 27} 31 | iex> %User{name: "eric"} 32 | %User{name: "eric", age: 27} 33 | iex> is_map(%User{}) 34 | true 35 | ``` 36 | 37 | struct提供了编译时的某一个数值存在于struct中的保证: 38 | 39 | ``` 40 | iex> %User{oops: :field} 41 | ** (CompileError) iex:3: unknown key :oops for struct User 42 | ``` 43 | 44 | 当我们谈论表单是,我们演示了我们才能访问和更新表单中的一个域。这同样适用于structs: 45 | 46 | ``` 47 | iex> jose = %User{} 48 | %User{name: "jose", age: 27} 49 | iex> jose.name 50 | "jose" 51 | iex> eric = %{jose | name: "eric"} 52 | %User{name: "eric", age: 27} 53 | iex> %{user | oops: :field} 54 | ** (ArgumentError) argument error 55 | ``` 56 | 57 | 有了更新的语法,虚拟机就能直到没有新的键会被加入表单和struct,运行表单在内存中共享它们的结构。在上面的例子中,`jose`和`eric`共享了通过一个内存中的键结构。 58 | 59 | Struct也能被用于模式匹配,它们保证了两边的struct都是统一类型的: 60 | 61 | ``` 62 | iex> %User{name: name} = jose 63 | %User{name: "jose", age: 27} 64 | iex> name 65 | "jose" 66 | iex> %User{} = %{} 67 | ** (MatchError) no match of right hand side value: %{} 68 | ``` 69 | 70 | 匹配之所以能完成,是因为struct在表单内部存储了一个叫`__struct__`的域: 71 | 72 | ``` 73 | iex> jose.__struct__ 74 | User 75 | ``` 76 | 77 | 总的来说,一个struct只不过是一个带有默认域的普通表单。注意我们说它是普通的表单因为实现的表单的所有协议,没有一个能被用于structs。例如,你不能对一个sturct进行枚举: 78 | 79 | ``` 80 | iex> user = %User{} 81 | %User{name: "jose", age: 27} 82 | iex> user[:name] 83 | ** (Protocol.UndefinedError) protocol Access not implemented for %User{age: 27, name: "jose"} 84 | ``` 85 | 86 | struct也不同于字典,所以模块``也对它们无效: 87 | 88 | ``` 89 | iex> Dict.get(%User{}, :name) 90 | ** (ArgumentError) unsupported dict: %User{name: "jose", age: 27} 91 | ``` 92 | 93 | 我们讲在下一章谈到struct如何同协议互动。 94 | -------------------------------------------------------------------------------- /getting_started/Chapter3.md: -------------------------------------------------------------------------------- 1 | # 3 基础操作符 2 | 3 | 在之前的几章中,我们了解了Elixir提供了`+`,`-`,`*`,`/`作为基本的运算操作符,外加函数`div/2`和`rem/2`用来做整数的除法和余数运算。 4 | 5 | Elixir也提供了 `++`和`--`用以修改列表: 6 | 7 | ``` 8 | iex> [1,2,3] ++ [4,5,6] 9 | [1,2,3,4,5,6] 10 | iex> [1,2,3] -- [2] 11 | [1,3] 12 | ``` 13 | 14 | 字符串连接操作符`<>`: 15 | 16 | ``` 17 | iex> "foo" <> "bar" 18 | "foobar" 19 | ``` 20 | 21 | Elixir也提供个是那个布尔值操作符`or`,`and`和`not`。这些操作符严格地指定它们一个参数必须是一个布尔值(`true`或者`false`): 22 | 23 | ``` 24 | iex> true and true 25 | true 26 | iex> false or is_atom(:example) 27 | true 28 | ``` 29 | 30 | 非布尔值会导致一个意外: 31 | 32 | ``` 33 | iex> 1 and true 34 | ** (ArgumentError) argument error 35 | ``` 36 | `or`和`and`是一对快捷操作符。只有当它们左侧的表达式不足以满足条件的时候才会执行右侧的表达式: 37 | 38 | ``` 39 | iex> false and error("This error will never be raised") 40 | false 41 | 42 | iex> true or error("This error will never be raised") 43 | true 44 | ``` 45 | 46 | > 注意:如果你是Erlang的开发者,Elixir中的`and`和`or`实际上映射到Erlang中的`andalso`和`orelse`操作符。 47 | 48 | 除此之外,Elixir也提供了`||`,`&&`和`!`,这些操作符接受任何类型的参数。对于这些操作,除了`false`和`nil`之外的所有值都为真: 49 | 50 | ``` 51 | # or 52 | iex> 1 || true 53 | 1 54 | iex> false || 11 55 | 11 56 | 57 | # and 58 | iex> nil && 13 59 | nil 60 | iex> true && 17 61 | 17 62 | 63 | # ! 64 | iex> !true 65 | false 66 | iex> !1 67 | false 68 | iex> !nil 69 | true 70 | ``` 71 | 72 | 简单来说,当你预期参数是布尔值的时候,用`and`,`or`和`not`。如果你不确定,那就使用`||`,`&&`和`!`。 73 | 74 | Elixir也提供了比较操作符`==`,`!=`,`===`,`!==`,`<=`,`>=`,`>`和`<`: 75 | 76 | ``` 77 | iex> 1 == 1 78 | true 79 | iex> 1 != 2 80 | true 81 | iex> 1 < 2 82 | true 83 | ``` 84 | 85 | `==`和`===`之间的不同在于,后者在比较整数和浮点数的时候更加严格: 86 | 87 | ``` 88 | iex> 1 == 1.0 89 | true 90 | iex> 1 === 1.0 91 | false 92 | ``` 93 | 94 | Elixir允许在不同类型之间进行比较: 95 | 96 | ``` 97 | iex> 1 < :atom 98 | true 99 | ``` 100 | 101 | 我们之所以可以在Elixir在不同数据类型之间比较是因为有一个内建的不同类型之间的顺序,所以算法在排序时无需担心数据类型上 的不同。下面就是内建的类型之间的顺序: 102 | 103 | ``` 104 | number < atom < reference < functions < port < pid < tuple < maps < list < bitstring 105 | ``` 106 | 107 | 你并不需要去记忆这些顺序,重要的是了解的确有这么一个东西。 108 | 109 | 好了,简单介绍到这里。在下一章中,我们将讨论函数的基础,数据类型之间的转换和一点流程控制。 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elixir [入门文档](http://elixir-lang.org/getting_started/1.html)中文(简体)翻译计划 2 | Translate [official elixir getting started guide](http://elixir-lang.org/getting_started/1.html) into simplified chinese 3 | 4 | ## 入门 5 | 6 | ### [第一章 - 互动模式(iex)](./getting_started/Chapter1.md) ✓ 7 | 8 | ### [第二章 - 基础数据结构](./getting_started/Chapter2.md) ✓ 9 | 10 | ### [第三章 - 基础操作符](./getting_started/Chapter3.md) ✓ 11 | 12 | ### [第四章 - 模式匹配](./getting_started/Chapter4.md) ✓ 13 | 14 | ### [第五章 - `case`,`cond`和`if`](./getting_started/Chapter5.md) ✓ 15 | 16 | ### [第六章 - 二进制,字符串和字符列表](./getting_started/Chapter6.md) ✓ 17 | 18 | ### [第七章 - 关键字,表单和字典](./getting_started/Chapter7.md) ✓ 19 | 20 | ### [第八章 - 模块](./getting_started/Chapter8.md) ✓ 21 | 22 | ### [第九章 - 递归](./getting_started/Chapter9.md) ✓ 23 | 24 | ### [第十章 - 可枚举类和流](./getting_started/Chapter10.md) ✓ 25 | 26 | ### [第十一章 - 进程](./getting_started/Chapter11.md) ✓ 27 | 28 | ### [第十二章 - IO](./getting_started/Chapter12.md) ✓ 29 | 30 | ### [第十三章 - `alias`,`require`和`import`](./getting_started/Chapter13.md) ✓ 31 | 32 | ### [第十四章 - 模块属性](./getting_started/Chapter14.md) ✓ 33 | 34 | ### [第十五章 - Structs](./getting_started/Chapter15.md) ✓ 35 | 36 | ### [第十六章 - 协议](./getting_started/Chapter16.md) ✓ 37 | 38 | ### [第十七章 - `try`,`catch`和`rescue`](./getting_started/Chapter17.md) ✓ 39 | 40 | ### [第十八章 - Comprehensions](./getting_started/Chapter18.md) ✓ 41 | 42 | ### [第十九章 - Sigils](./getting_started/Chapter19.md) ✓ 43 | 44 | ### [第二十章 - 更多内容](./getting_started/Chapter20.md) ✓ 45 | 46 | 47 | ## Mix - Elixir的编译工具 48 | 49 | ### [第一章 - 简介](./mix/Chapter1.md) ✓ 50 | 51 | ### [第二章 - 用Mix来编译OTP应用](./mix/Chapter2.md) ✓ 52 | 53 | ### [第三章 - 定制Mix任务](./mix/Chapter3.md) ✓ 54 | 55 | 56 | ## EXUNIT - 单元测试工具 57 | 58 | ### [第一章 - 简介](./exunit/Chapter1.md) 59 | 60 | 61 | ## Elixir元编程 62 | 63 | ### 第一章 - 引用和去引用 64 | 65 | ### 第二章 - 宏 66 | 67 | ### 第三章 - 领域特定语言 68 | -------------------------------------------------------------------------------- /getting_started/Chapter18.md: -------------------------------------------------------------------------------- 1 | # 18 Comprehensions 2 | 3 | 在Elixir中,常常用到循环历遍一个可枚举类,用于过滤结果,和映射到另一个列表的值上。 4 | 5 | Comprehension是对这一中结构的语法糖, 把这些常见的任务用一个特殊的形式`for`组织起来。 6 | 7 | 例如,我们能用下面的方式得到一个列表值的平方: 8 | 9 | ``` 10 | iex> for n <- [1, 2, 3, 4], do: n * n 11 | [1, 4, 9, 16] 12 | ``` 13 | 14 | 一个Compreshension由三个部分组成: 生成器,过滤器和集合。 15 | 16 | ## 18.1 生成器和过滤器 17 | 18 | 在上面的表达式中,`n <- [1, 2, 3,4]`是生成器。产出了供后面comprehension的值。任何的可枚举类都能被放置在产生器表达式的右侧: 19 | 20 | ``` 21 | iex> for n <- 1..4, do: n * n 22 | [1, 4, 9, 16] 23 | ``` 24 | 25 | 生成器表达式也支持模式匹配,来丢弃所有不匹配的模式。想象一下假如不用范围,我们有一个关键字列表,这个列表有两种键`:good `和`:bad`。而我们只关心好的值的计算结果: 26 | 27 | ``` 28 | iex> values = [good: 1, good: 2, bad: 3, good: 4] 29 | iex> for {:good, n} <- values, do: n * n 30 | [1, 4, 16] 31 | ``` 32 | 33 | 除此之外,过滤器能被用于过滤一些特定的元素。例如, 我们能只计算奇数的平方: 34 | 35 | ``` 36 | iex> require Integer 37 | iex> for n <- 1..4, Integer.odd?(n), do: n * n 38 | [1, 9] 39 | ``` 40 | 41 | 一个过滤器会保留除了`false`和`nil`之外的所有值。 42 | 43 | 相比直接使用`Enum`和`Stream `模块中的函数,comprehension提供了一种简洁的多的表现形式。而且,comprehension也允许多个生成器和过滤器。这里是一个例子用来接受一个文件夹的列表,然后删除每个文件夹中的所有文件: 44 | 45 | ``` 46 | for dir <- dirs, 47 | file <- File.ls!(dir), 48 | path = Path.join(dir, file), 49 | File.regular?(path) do 50 | File.rm!(path) 51 | end 52 | ``` 53 | 54 | 谨记,在comprehension内不赋值的变量,无论是在生成器内,还是在过滤器内,不会影响到comprehension外的环境 55 | 56 | ## 18.2 比特串生成器 57 | 58 | 比特串生成器也支持的,而且当你需要组织比特串流的时候会非常有用。下面的这个例子从一个二进制接受一个列表的像素,每个像素用红绿蓝三色的数值表示,把它们转成三元组。 59 | 60 | ``` 61 | iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>> 62 | iex> for <>, do: {r, g, b} 63 | [{213,45,132},{64,76,32},{76,0,0},{234,32,15}] 64 | ``` 65 | 66 | 一个比特串的生成器能和“普通”的可枚举类产生器混合,并提供过滤器。 67 | 68 | # 18.3 Into 69 | 70 | 在上面的例子中,comprehension返回一个列表作为结果。 71 | 72 | 然而,用传递`:into`选项,compreshension的结果能被插入不同的数据结果。例如,我们能用比特串产生器和`:into`选项来轻松地删除字符串中的所有空格: 73 | 74 | ``` 75 | iex> for <>, c != ?\s, into: "", do: <> 76 | "helloworld" 77 | ``` 78 | `Set`, `maps`和其他类型的字典也能被给予`:into`选项。总的来说,`:into`接受任何一种数据结构,只要它实现了`Collectable`协议。 79 | 80 | 例如,模块提供的流,它们既是`Enumerable`又是`Collectable`。你能用comprehension实现一个echo控制台,它返回所有接受到的输入和大写形式。 81 | 82 | ``` 83 | iex> stream = IO.stream(:stdio, :line) 84 | iex> for line <- stream, into: stream do 85 | ...> String.upcase(line) <> "\n" 86 | ...> end 87 | ``` 88 | 89 | 如果你在那个控制台里输入任何字符串,你将会看到同样的值会以大写的形式被打印出来。不幸的是,这个comprehension会锁死你的终端,所以你必须敲击两次``才能退出程序。:) 90 | -------------------------------------------------------------------------------- /getting_started/Chapter9.md: -------------------------------------------------------------------------------- 1 | # 递归 2 | 3 | 由于在函数式编程语言中数据是不可变的,Elixir中的循环的写法和普通的命令式语言是不同的。例如,在某种命令式语言中,可能是这样的: 4 | 5 | ``` 6 | for(i = 0; i < array.length; i++) { 7 | array[i] = array[i] * 2 8 | } 9 | ``` 10 | 在上面的例子中,我们改变了数组和变量`i`。在Elixir中这是不行的。事实上,Elixir这样的函数式语言依赖于递归:一个函数被反复地调用直到达到一个界限。下面的这个例子中,用来多次打印一个字符串: 11 | 12 | ``` 13 | defmodule Recursion do 14 | def print_multiple_times(msg, n) when n <= 1 do 15 | IO.puts msg 16 | end 17 | 18 | def print_multiple_times(msg, n) do 19 | IO.puts msg 20 | print_multiple_times(msg, n - 1) 21 | end 22 | end 23 | 24 | Recursion.print_multiple_times("Hello!", 3) 25 | # Hello! 26 | # Hello! 27 | # Hello! 28 | ``` 29 | 30 | 和例子类似,一个函数可能有多个子句。某一个子句只有在接受到的参数匹配自己的模式而且它的守护返回`true`的时候才会被执行。 31 | 32 | 在上面的例子中,当函数`print_multiple_times/2 `被初次调用的时候,参数`n`的值是`3`。 33 | 34 | 第一个子句有一个守护,只有当`n`小于等于`1`的时候才满足条件。显然这个条件目前无法满足,所以Elixir选择执行第二个子句。 35 | 36 | 第二个定义满足定义而且没有守护,所以它被执行了。首先它打印出`msg`,然后用`n - 1(2)`作为第二个参数再调用自身。我们的`msg`再次被打印了,然后函数`print_multiple_times/2 `被调用,这一次第二个参数的值是`1`。 37 | 38 | 因为现在`n`为`1`, 函数``的第一个定义的守护返回`true`,这个定义就被执行了。`msg`再一次被打印,完成整个过程。 39 | 40 | 根据我们对`print_multiple_times/2 `的定义,不管第二个参数是什么,要么执行第一个定义(又称“基本例子”)或者执行我们的第二个定义,它的每次调用都是我们离基本定义更进一步。 41 | 42 | 现在让我们看看如何利用递归的威力来加总一个列表中的数字: 43 | 44 | ``` 45 | defmodule Math do 46 | def sum_list([head|tail], accumulator) do 47 | sum_list(tail, head + accumulator) 48 | end 49 | 50 | def sum_list([], accumulator) do 51 | accumulator 52 | end 53 | end 54 | 55 | Math.sum_list([1, 2, 3], 0) #=> 6 56 | ``` 57 | 58 | 我们用列表`sum_list`和初始值0作为参数来调用`[1,2,3]`.我们会尝试每一个子句直到找到匹配模式规则的那个。在这个例子中,列表`[1,2,3]`匹配模式`[head|tail]`,于是`head = 1`和`tail = [2,3]`, 而累计值`accumulater`被设为`0`; 59 | 60 | 接着我们列表的有和累计值相加`head + accumulator`,然后再次调用`sum_list`,同时把列表的尾作为第一个参数。因为尾也是一个列表,所以可以一次又一次匹配`[head|tail]`,直到列表为空,然我们看一看下面的过程: 61 | 62 | ``` 63 | sum_list [1, 2, 3], 0 64 | sum_list [2, 3], 1 65 | sum_list [3], 3 66 | sum_list [], 6 67 | ``` 68 | 69 | 当列表为空的时候,最后一个子句就被匹配了,它返回最终的结果,`6`。 70 | 71 | 这样的把一个列表的中元素个数递减到一个的过程,称为“递减”算法,它是函数式编程的基础之一。 72 | 73 | 如果我们要把一个列表中的数值都倍增,该怎么办? 74 | 75 | ``` 76 | defmodule Math do 77 | def double_each([head|tail]) do 78 | [head * 2| double_each(tail)] 79 | end 80 | 81 | def double_each([]) do 82 | [] 83 | end 84 | end 85 | 86 | Math.double_each([1, 2, 3]) #=> [2, 4, 6] 87 | ``` 88 | 89 | 在这里我们用递归遍历了整个列表并且把每个元素都加倍,然后返回一个新的列表。这种把历遍列表中每个元素的的过程被称为“映射”算法。 90 | 91 | 递归和尾调优化是Elixir重要的组成部分,常用于创建循环。然而,在实际的Elixir编程中你其实并不需要想上面的例子那样来做。 92 | 93 | 在我们下一章就要讲到的[Enum](http://elixir-lang.org/docs/stable/Enum.html)模块中,已经为处理列表提供了很多的方便。比如,上面的例子可以被写成这样: 94 | 95 | ``` 96 | iex> Enum.reduce([1, 2, 3], 0, fn(x, acc) -> x + acc end) 97 | 6 98 | iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end) 99 | [2, 4, 6] 100 | ``` 101 | 或者,使用捕捉语法: 102 | 103 | ``` 104 | iex> Enum.reduce([1, 2, 3], 0, &+/2) 105 | 6 106 | iex> Enum.map([1, 2, 3], &(&1 * 2)) 107 | [2, 4, 6] 108 | ``` 109 | 110 | 接下去让我们仔细的研究一下可枚举类和流。 111 | -------------------------------------------------------------------------------- /getting_started/Chapter1.md: -------------------------------------------------------------------------------- 1 | # 1 Elixir的交互模式,iex 2 | 3 | 欢迎! 4 | 5 | 在这个系列的教程中,我们将学习elixr的一些基本知识。在这章之中将会涉及到如何安装和如何使用elixir的交互模式,iex。 6 | 7 | [如果你在教程或者网站上发现了错误,请提交报告或者发一个pull request到我们的issue tracker上](https://github.com/kuno/elixir_guide_cn/issues)。如果你怀疑可能遇到了一个语言本身的bug,[请汇报至elixir语言的issue tracker](https://github.com/elixir-lang/elixir/issues)。 8 | 9 | 让我们开始吧! 10 | 11 | ## 1.1 安装Erlang 12 | 13 | Elixir依赖于17.0版或者更新的Erlang,你可以很容易地通过[预编译的包](https://www.erlang-solutions.com/downloads/download-erlang-otp)来安装。如果你想直接从源代码安装,你可以直接从[Erlang的官方网站](http://www.erlang.org/download.html)上下载源代码,在Riak的官方文档中有一篇非常详尽的[教程](http://docs.basho.com/riak/1.3.0/tutorials/installation/Installing-Erlang/)会指导你如何安装。 14 | 15 | 如果你打算在Windows上开发,最佳的方式是安装预编译的安装包。如果你是在一个类Unix的系统上,一般来说你系统上的包管理器很可能已经提供了相应的安装包。 16 | 17 | > 注意,Elixir需要的是17.0以上的版本的Erlang,由于17.0刚发布不久,你系统上的包管理器很可能还没有升级。 18 | 19 | 安装完Erlang之后,你应该检查一下安装的结果,在命令行中键入`erl`,如果一切正常你应该能看到这样的一条信息: 20 | 21 | ``` 22 | Erlang/OTP 17 (erts-6) [64-bit] [smp:2:2] [async-threads:0] [hipe] [kernel-poll:false] 23 | ``` 24 | 25 | 根据你如何安装,在某些情况下Erlang的二进制命令可能不在你的[`PATH`](http://en.wikipedia.org/wiki/Environment_variable)上。那样的话,你就需要手动地把它们加到`PATH`里面去。 26 | 27 | 在Erlang一切就绪之后,我们就可以安装Elixir了。你可以通过三种方式安装,发行版,预编译包和从源代码安装。 28 | 29 | ## 1.2 发行版 30 | 31 | 到目前为止,Elixir v0.13有希望在某些os的发行版上直接安装了: 32 | 33 | * Mac OSX上的Homebrew 34 | * 升级Homebrew到最新`brew update` 35 | * 安装Elixir:`brew install elixir` 36 | 37 | * Fedora 17+和Fedora Rawhide 38 | * `sudo yum -y install elixir` 39 | 40 | * Arch Linux (Aur) 41 | * `yaourt -S elixir` 42 | 43 | * OpenSUSE (和SLES 11 SP3+) 44 | * 添加Erlang开发源 `zypper ar -f obs://devel:languages:erlang/ erlang` 45 | * 安装Elixir `zypper in elixir` 46 | 47 | * Gentoo 48 | * `emerge --ask dev-lang/elixir` 49 | 50 | * Chocolatey for Windows 51 | * `cinst elixir` 52 | 53 | 如果你用的OS并不是以上的任何之一,别担心,我们也提供了已经编译好的安装包! 54 | 55 | ## 1.3 预编译安装包 56 | 57 | Elixir的每一个release都提供了[预先编译好的安装包](https://github.com/elixir-lang/elixir/releases/)。只要下载之后解压,就可以之间受用`bin`目录下的`elixr`和`iex`命令。同时我们也建议你把`bin`目录加入到系统的`PATH` 58 | 里去,这样就能为以后的开发省去不少的麻烦。 59 | 60 | ## 1.4 从源代码编译(Unix和MinGW) 61 | 62 | 只需简单的几步就可以下载和编译Elixir。你可以这里下载Elixir的[最新的代码](https://github.com/elixir-lang/elixir/releases/),在解压缩之后的目录里运行`make`。如果没有出错,你就可以运行`bin`目录下的`elixir`和`iex`命令。同样,我们也建议把`bin`所在的目录加入`PATH`。 63 | 64 | ``` 65 | export PATH="$PATH:/path/to/elixir/bin" 66 | ``` 67 | 68 | 万一你想尝鲜的话,你还可以从git master编译: 69 | 70 | ``` 71 | $ git clone https://github.com/elixir-lang/elixir.git 72 | $ cd elixir 73 | $ make clean test 74 | ``` 75 | 76 | 如果上面的测试都通过了,就表明一切就绪。否则,请提交一个错误报告在[Github的issue tracker](https://github.com/elixir-lang/elixir)上。 77 | 78 | ## 1.5 互动模式 79 | 80 | Elixir提供了三个新的可执行命令:`iex`, `elixir`和`elixirc`。如果你是从源代码编译或者直接用的打包好的,你会发现这三个命令在`bin`目录下。 81 | 82 | 现在,让我们开始常识着运行互动模式`iex`, `iex`是“互动Elixir”(Interactive Elixir)的缩写。在互动模式中,我们可以输入任何的Elixir表达式,直接可以看到运行的结果。让我们运行几个例子来热热身: 83 | 84 | ``` 85 | Interactive Elixir - press Ctrl+C to exit (type h() ENTER for help) 86 | 87 | iex> 40 + 2 88 | 42 89 | iex> "hello" <> " world" 90 | "hello world" 91 | ``` 92 | 93 | 看起来我们已经一切就绪了!在下面的章节之中,我们将会频繁地使用到互动模式,它会帮助我们熟悉语言的结构和基本数据类型。就从下一章开始。 94 | -------------------------------------------------------------------------------- /getting_started/Chapter11.md: -------------------------------------------------------------------------------- 1 | # 11 进程 2 | 3 | Elixir的所有代码都运行在进程。每个进程之间互相隔离,同时运行,通过传递消息进行通讯。进程不仅是Elixir并发的基础,而且它们也是编写分布式和高可靠性程序的基础。 4 | 5 | 和其他语言中的线程不同,就内存和CPU消耗来说,进程是极为轻量级的。同时运行上千个进程是家常便饭。 6 | 7 | 在这一章,我们将学习如何创建一个新的进程,包括在进程之间首发信息。 8 | 9 | ## 11.1 spawn 10 | 11 | 创建一个进程最基本的方式是使用内置的`spawn/1`函数: 12 | 13 | ``` 14 | iex> spawn fn -> 1 + 2 end 15 | #PID<0.43.0> 16 | ``` 17 | 18 | `spawn/1`接受一个函数,然后在另一个进程中执行。 19 | 20 | 注意`spawn/1`返回一个PID(进程识别符)。在这个时候看起来你的进程很可能已经死掉了。被创建的进程在执行完传进来的函数之后会自动退出: 21 | 22 | ``` 23 | iex> pid = spawn fn -> 1 + 2 end 24 | #PID<0.44.0> 25 | iex> Process.alive?(pid) 26 | false 27 | ``` 28 | 29 | > 注意:自己动手的话,你应该会得到一个和我们上面例子不同的PID 30 | 31 | 我们能通过调用`self/0`得到当前进程的PID: 32 | 33 | ``` 34 | iex> self() 35 | #PID<0.41.0> 36 | iex> Process.alive?(self()) 37 | true 38 | ``` 39 | 40 | 只有当我们能够在进程之间首发信息是,它才会变得更加有趣。 41 | 42 | ## 11.2 发和送 43 | 44 | 我们能用`send/2`向一个进程发送信息,能用`receive/1`接受信息。 45 | 46 | ``` 47 | iex> send self(), {:hello, "world"} 48 | {:hello, "world"} 49 | iex> receive do 50 | ...> {:hello, msg} -> msg 51 | ...> {:world, msg} -> "won't match" 52 | ...> end 53 | "world" 54 | ``` 55 | 56 | 当一个消息被送到一个进程的时候,它首先被储存在进程的邮箱里。`receive/2`会在邮箱中寻找第一个匹配模式的消息,`receive/1`支持许多子句,比如`case/2`,包括子句中的守护。 57 | 58 | 如果邮箱中没有消息匹配指定的模式,当前的进程会一直等待直到来了一个匹配的消息。当然你可以指定等待的最长时间: 59 | 60 | ``` 61 | iex> receive do 62 | ...> {:hello, msg} -> msg 63 | ...> after 64 | ...> 1_000 -> "nothing after 1s" 65 | ...> end 66 | "nothing after 1s" 67 | ``` 68 | 69 | 如果早就确信消息就在邮箱里,那可以把timeout设为0。 70 | 71 | 让我们汇总一下看看如何在进程间发送消息: 72 | 73 | ``` 74 | iex> parent = self() 75 | #PID<0.41.0> 76 | iex> spawn fn -> send(parent, {:hello, self()}) end 77 | #PID<0.48.0> 78 | iex> receive do 79 | ...> {:hello, pid} -> "Got hello from #{inspect pid}" 80 | ...> end 81 | "Got hello from #PID<0.48.0>" 82 | ``` 83 | 84 | 如果你在控制台里,那函数`flush/0`可能就对你比较有用。它能打印并清空整个邮箱。 85 | 86 | ``` 87 | iex> send self(), :hello 88 | :hello 89 | iex> flush() 90 | :hello 91 | :ok 92 | ``` 93 | 94 | 在我们完成这章之前,让我们谈一谈进程之间的链接。 95 | 96 | # 11.3 链接 97 | 98 | 实际上,在Elixir中最长用到的创建进程的方式是调用函数`spawn_link/1`。在展示`spawn_link/1`的例子之前,让我们试着看看当进程出错的时候会发生什么: 99 | 100 | ``` 101 | iex> spawn fn -> raise "oops" end 102 | #PID<0.58.0> 103 | ``` 104 | 105 | 正如你所见。。。什么也没有发生。那是因为进程都是独立的。如果我们希望一个进程的问题能够传导至另一个进程,我们就应该把它们链接起来。这个时候就该`spawn_link/1`出场了: 106 | 107 | ``` 108 | iex> spawn_link fn -> raise "oops" end 109 | #PID<0.60.0> 110 | ** (EXIT from #PID<0.60.0>) {RuntimeError[message: "oops"], [{:erlang, :apply, 2, []}]} 111 | ``` 112 | 113 | 当控制台中除了错,控制台自动处理它们,并且打印出整齐的错误信息。为了能真正理解刚才在我们的代码中发生了什么,让我们使用在一个脚本中使用`spawn_link/1`,并运行看看: 114 | 115 | ``` 116 | # spawn.exs 117 | spawn_link fn -> raise "oops" end 118 | 119 | receive do 120 | :hello -> "let's wait until the process fails" 121 | end 122 | ``` 123 | 124 | 这一次,当子进程退出,也使得母进程退出了,因为它们是链接在一起的。链接可以通过调用`Process.link/2`手动完成。我们见你你看看[Process模块](http://elixir-lang.org/docs/stable/Process.html)中还有那些其他的函数。 125 | 126 | 在编写高可靠性的系统时, 进程和链接扮演了重要的角色。在Elixir的应用中,我们常常把我们的进程同监工链接在一起,它的任务是当发现有进程退出的时候,在原地重启一个新的进程。之所以能这么干,就是得益于进程之间互相独立,某一个进程的问题不会导致其他进程出问题。 127 | 128 | 在其他的一些语言中,它也许要求你捕捉和处理意外,在Elixir的中我们可以随它去,因为我们直到监工会去重启进程。“早出问题”是编写Elixir软件时的常用哲学思想。 129 | 130 | 下面我们将探索IO的世界。 131 | -------------------------------------------------------------------------------- /getting_started/Chapter10.md: -------------------------------------------------------------------------------- 1 | # 10 可枚举类和流 2 | 3 | ## 10.1 可枚举类 4 | 5 | Elixir中提供了可枚举类,和用于处理其的的[Enum模块](http://elixir-lang.org/docs/stable/Enum.html)。我们已经见到过两种可枚举类了,列表和表单: 6 | 7 | ``` 8 | iex> Enum.map([1, 2, 3], fn x -> x * 2 end) 9 | [2, 4, 6] 10 | iex> Enum.map(%{1 => 2, 3 => 4}, fn {k, v} -> k * v end) 11 | [2, 12] 12 | ``` 13 | 14 | `Enum`模块提供了众多的函数用以转换,排序,组织,过滤和获取可枚举类中的元素。在Elixir中它是开发者最经常使用的模块之一。 15 | 16 | Elixir也支持`range`: 17 | 18 | ``` 19 | iex> Enum.map(1..3, fn x -> x * 2 end) 20 | [2, 4, 6] 21 | iex> Enum.reduce(1..3, 0, &+/2) 22 | 6 23 | ``` 24 | 25 | 因为Enum模块一设计开始就是着眼于能和不同的数据类型打交道,它的API只包含那些能通用于不同数据类型的函数。对一些特殊的操作,你就可能需要针对那个数据类型的模块了。例如,如果你要把一个元素插入到一个列表的特定位置,你应该使用`List`模块中的`List.insert_at/3`。插入一个值到一个范围中可能就意义不大。 26 | 27 | 我们说Enum模块中的函数是多态的,是指它们能够通用在不同类型的数据上。实际上,Enum模块的函数可以用在任何实现了[可枚举协议](http://elixir-lang.org/docs/stable/Enumerable.html)的任何数据类型上。我们将在下面的章节中讨论协议,现在我们要讨论可枚举类中的一个特例,流。 28 | 29 | ## 10.2 即刻 vs 懒惰 30 | 31 | Enum模块中的所有函数都是饥渴的。许多函数接受一个可枚举类,并且返回一个列表。 32 | 33 | ``` 34 | iex> odd? = &(rem(&1, 2) != 0) 35 | #Function<6.80484245/1 in :erl_eval.expr/5> 36 | iex> Enum.filter(1..3, odd?) 37 | [1, 3] 38 | ``` 39 | 40 | 这意味着当进行连续的函数调用是,每一个调用都会产生一个列表,直到最后结果为止。 41 | 42 | ``` 43 | iex> 1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum 44 | 7500000000 45 | ``` 46 | 47 | 上面是一个管道操作的例子。我们从一个管道开始,对其中的每一个元素都乘以`3`。第一个操作会返回一个包含`1000_000`个成员的列表。接着我们返回由其中所有的奇数成员组成的列表。最后我们在加总这个有`50_000`个成员的列表。 48 | 49 | 初次之外,Elixir还有[流模块](http://elixir-lang.org/docs/stable/Stream.html),提供了懒惰操作: 50 | 51 | ``` 52 | iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) |> Enum.sum 53 | 7500000000 54 | ``` 55 | 56 | 流模块并不产生中间性的列表,相反所有的操作都只有达到Enum模块的时候才会被触发。流模块在处理非常大,甚至_可能无限_长的集合的时候非常有用。 57 | 58 | ## 10.3 流 59 | 60 | 流是懒惰的,能降解的可枚举类。 61 | 62 | 说它懒惰是因为,就像在上面的例子中显示的,`1..100_000 |> Stream.map(&(&1 * 3))`返回一个数据类型,是一个流,它表示对返回`1..100_000`的`map`计算。 63 | 64 | ``` 65 | iex> 1..100_000 |> Stream.map(&(&1 * 3)) 66 | #Stream<1..100_000, funs: [#Function<34.16982430/1 in Stream.map/2>]> 67 | ``` 68 | 69 | 进一步说,它是可降解的因为我们能通过管道连接很多流: 70 | 71 | ``` 72 | iex> 1..100_000 |> Stream.map(&(&1 * 3)) |> Stream.filter(odd?) 73 | #Stream<1..100_000, funs: [...]> 74 | ``` 75 | 76 | 许多流模块中的函数接受任何一种可枚举类并返回一个流作为结果。它同时也提供了创建普通的,甚至是无限的流的函数。例如,`Stream.cycle/1`能被用于创建一个在给定的可枚举类上无限往复的流。注意不要对这样的流使用类似`Enum.map/2 `这样的函数,这回导致一个死循环: 77 | 78 | ``` 79 | iex> stream = Stream.cycle([1, 2, 3]) 80 | #Function<15.16982430/2 in Stream.cycle/1> 81 | iex> Enum.take(stream, 10) 82 | [1, 2, 3, 1, 2, 3, 1, 2, 3, 1] 83 | ``` 84 | 85 | 在另一方面,`Stream.unfold/2`能被用于从一个初始值产生一个流: 86 | 87 | ``` 88 | iex> stream = Stream.unfold("hełło", &String.next_codepoint/1) 89 | #Function<15.16982430/2 in Stream.cycle/1> 90 | iex> Enum.take(stream, 3) 91 | ["h", "e", "ł"] 92 | ``` 93 | 94 | 另一个有趣的函数是`Stream.resource/3`,它能被用来对资源进行处理,确保即时在失败的情况下,资源也能在枚举开始之前被打开,结束之后在关闭。例如,我们可以用它来流化一个文件: 95 | 96 | ``` 97 | iex> stream = File.stream!("path/to/file") 98 | #Function<18.16982430/2 in Stream.resource/3> 99 | iex> Enum.take(stream, 10) 100 | ``` 101 | 102 | 上面的例子会你选中的文件的前10行。这显示了流在处理大文件或网络资源之类的慢资源的时候会非常有用。 103 | 104 | 不要被[Enum模块](http://elixir-lang.org/docs/stable/Enum.html)和[Stream模块](http://elixir-lang.org/docs/stable/Stream.html)中的函数的个数和复杂性吓坏了,随着用的越多,你很多就会熟悉它们。我们建议你,首先聚焦在Enum模块中,只有当你在和慢资源或非常巨大,近乎无限的资源打交道,懒惰对你非常重要是才选择流。 105 | 106 | 下面,我们将研究Elixir中的一个核心特性,进程。它允许我们用一个简单而又容易理解的方式编写并行,分布式的程序。 107 | -------------------------------------------------------------------------------- /getting_started/Chapter4.md: -------------------------------------------------------------------------------- 1 | # 模式匹配 2 | 3 | 在这章中我们将看到`=`操作符在Elixir中实际上是一个匹配操作符,和如何用它来匹配一个数据结构中的模式,我们将学习用定位操作符`^`来访问之前绑定的值。 4 | 5 | ## 4.1 匹配操作符 6 | 7 | 我们之前在Elixir中已经多次使用`=`操作符来进行复制了: 8 | 9 | ``` 10 | iex> x = 1 11 | 1 12 | iex> x 13 | 1 14 | ``` 15 | 16 | 实际上,在`Elixir`里,`=`操作符被称为匹配操作符。这是为什么: 17 | 18 | ``` 19 | iex> 1 = x 20 | 1 21 | iex> 2 = x 22 | ** (MatchError) no match of right hand side value: 1 23 | ``` 24 | 注意`1 = x`是一个合法的表达式,它能够匹配是因为左右两侧都等于1.当两侧不相等时,会导致一个`MatchError`错误。 25 | 26 | 只有当变量位于`=`左侧的是,才能复制: 27 | 28 | ``` 29 | iex> 1 = unknown 30 | ** (RuntimeError) undefined function: unknown/0 31 | ``` 32 | 33 | 在上面的例子中,变量`unknow`之前并没有被定义,Elixir于是认为以在试图调用一个函数`unknown/0`, 当然一个函数也并不存在。 34 | 35 | 36 | ## 4.2 模式匹配 37 | 38 | 匹配操作符不仅用于匹配单个变量,也能用于结构复杂的数据类型。例如,我们可以匹配元组: 39 | 40 | ``` 41 | iex> {a, b, c} = {:hello, "world", 42} 42 | {:hello, "world", 42} 43 | iex> a 44 | :hello 45 | iex> b 46 | "world" 47 | ``` 48 | 49 | 当两边不匹配的时候会报错。举个例子,当两边的元组大小不一样时: 50 | 51 | ``` 52 | iex> {a, b, c} = {:hello, "world"} 53 | ** (MatchError) no match of right hand side value: {:hello, "world"} 54 | ``` 55 | 56 | 或者当试图匹配不同类型是: 57 | 58 | ``` 59 | iex> {a, b, c} = [:hello, "world", "!"] 60 | ** (MatchError) no match of right hand side value: [:hello, "world", "!"] 61 | ``` 62 | 63 | 更有趣的是,我们可以匹配指定的值。下面的例子假设只有当右侧是一个一原子`:ok`开头的元组的时候,左侧才匹配: 64 | 65 | ``` 66 | iex> {:ok, result} = {:ok, 13} 67 | {:ok, 13} 68 | iex> result 69 | 13 70 | 71 | iex> {:ok, result} = {:error, :oops} 72 | ** (MatchError) no match of right hand side value: {:error, :oops} 73 | ``` 74 | 75 | 我们能对列表进行模式匹配: 76 | 77 | ``` 78 | iex> [a, b, c] = [1, 2, 3] 79 | [1, 2, 3] 80 | iex> a 81 | 1 82 | ``` 83 | 84 | 列表也支持对头和尾进行匹配: 85 | 86 | ``` 87 | iex> [head | tail] = [1, 2, 3] 88 | [1, 2, 3] 89 | iex> head 90 | 1 91 | iex> tail 92 | [2, 3] 93 | ``` 94 | 95 | 和函数`hd/1`和`tl/1`类似,我们无法匹配对一个空列表匹配头尾: 96 | 97 | ``` 98 | iex> [h|t] = [] 99 | ** (MatchError) no match of right hand side value: [] 100 | ``` 101 | 102 | `[head | tail]`不仅能用于模式匹配,而且被用于前插元素到一个列表: 103 | 104 | ``` 105 | iex> list = [1, 2, 3] 106 | [1, 2, 3] 107 | iex> [0|list] 108 | [0, 1, 2, 3] 109 | ``` 110 | 111 | 模式识别能让开发者非常容易地解构数据类型,不仅能用于元组和列表,也适用于表单(map)和二进制。在接下去的几章中,我们将看到,这是构成Elixir中的函数递归的基础之一。 112 | 113 | ## 4.3 定位操作符 114 | 115 | Elixir中的变量允许重新绑定: 116 | 117 | ``` 118 | iex> x = 1 119 | 1 120 | iex> x = 2 121 | 2 122 | ``` 123 | 124 | 当你不想重新绑定一个变量,只是想匹配当前绑定的值的时候,定位操作符``就有了用武之地: 125 | 126 | ``` 127 | iex> x = 1 128 | 1 129 | iex> ^x = 2 130 | ** (MatchError) no match of right hand side value: 2 131 | iex> {x, ^x} = {2, 1} 132 | {2, 1} 133 | iex> x 134 | 2 135 | ``` 136 | 137 | 注意如果一个变量在一个模式中出现了两次以上,所有的引用都必须指向同一个模式: 138 | 139 | ``` 140 | iex> {x, x} = {1, 1} 141 | 1 142 | iex> {x, x} = {1, 2} 143 | ** (MatchError) no match of right hand side value: {1, 2} 144 | ``` 145 | 146 | 在很多情况下,你不需要模式中的所有值。在这种情况下,一个常见的做法是将这些不需要的值绑定到下划线,_。例如,如果一个列表中只有头是重要的话,我们可以我尾绑定到下划线: 147 | 148 | ``` 149 | iex> [h | _] = [1, 2, 3] 150 | [1, 2, 3] 151 | iex> h 152 | 1 153 | ``` 154 | 155 | `_`是一个特殊的变量,它是无法被读取的。试图去读它的值会导致一个未绑定变量错误: 156 | 157 | ``` 158 | iex> _ 159 | ** (CompileError) iex:1: unbound variable _ 160 | ``` 161 | 162 | 虽然模式匹配允许我们构建非常强大的结构,它并不是万能的。比如,你无法在匹配的左侧调用函数。下面是一个非法的例子: 163 | 164 | ``` 165 | iex> length([1,[2],3]) = 3 166 | ** (ErlangError) erlang error :illegal_pattern 167 | ``` 168 | 169 | 关乎模式匹配就到此告一段落。在下一章中我们将看到模式匹配被广泛地应用在许多的语言层面的构件中。 170 | -------------------------------------------------------------------------------- /exunit/Chapter1.md: -------------------------------------------------------------------------------- 1 | # 1 简介 2 | 3 | ExUnit是Elixir自带的单元测试框架。 4 | 5 | ExUnit很容易使用,下面是一个最简单的例子: 6 | 7 | ``` 8 | ExUnit.start 9 | 10 | defmodule MyTest do 11 | use ExUnit.Case 12 | 13 | test "the truth" do 14 | assert true 15 | end 16 | end 17 | ``` 18 | 19 | 总的来说,我们仅仅需要调用`ExUnit.start`,用`ExUnit.Case`定义一个测试实例,以及具体的测试。大部分的测试都作为[Mix](http://elixir-lang.org/getting_started/mix/1.html)项目的一部分: 20 | 21 | ``` 22 | mix test 23 | ``` 24 | 25 | 不然的话,假设你把这个文件存成一个文件`assertion_test.exs`,我们可以直接运行: 26 | 27 | ``` 28 | bin/elixir assertion_test.exs 29 | ``` 30 | 31 | 在这一章,我们将讨论ExUnit中最常用的特性和如何进一步定制它。 32 | 33 | # 1.1 开始ExUnit 34 | 35 | ExUnit通常以`ExUnit.start`开头。这个函数接受几个选项,这个[文档](http://elixir-lang.org/docs/stable/ExUnit.html)里有更详细的内容。现在,我们只点出几个最常用的: 36 | 37 | * `:formatter` - 当你运行测试的时候,所有的IO都是由[formatter](https://github.com/elixir-lang/elixir/blob/master/lib/ex_unit/lib/ex_unit/formatter.ex)完成的。开发者能定义它们自己的formatter, 用这个配置能让ExUnit知道去使用定制化的formatter。 38 | 39 | * `:max_cases` - 我们很快就将看到,ExUnit能让你非常容易地并行运行测试。这可以加快你的测试进度,而且没有副作用。这个选项运行你配置ExUnit最多能同时运行的测试的个数。 40 | 41 | # 1.2 定义一个测例 42 | 43 | 一旦ExUnit开始了,我们能定义我们自己的测例。这是由我们的模块中的`ExUnit.Case`完成的: 44 | 45 | ``` 46 | use ExUnit.Case 47 | ``` 48 | 49 | `ExUnit.Case`提供了几个函数,让我们来探查一番。 50 | 51 | ## 1.2.1 宏`test` 52 | 53 | `ExUnit.Case`运行之中所有名字以`test`开始,并且接受单个参数的的函数: 54 | 55 | ``` 56 | def test_the_truth(_) do 57 | assert true 58 | end 59 | ``` 60 | 61 | 作为一种定义上面那样的测例的简便方式,`ExUnit.Case`提供了一个宏`test`,可以写成这样: 62 | 63 | ``` 64 | test "the truth" do 65 | assert true 66 | end 67 | ``` 68 | 69 | 这种结构可读性更高。宏`test`接受二进制或原子作为名字。 70 | 71 | 72 | ## 1.2.2 断言 73 | 74 | 另一个由`ExUnit.Case`提供的简便方式,是通过[`ExUnit.Assertions`](http://elixir-lang.org/docs/stable/ExUnit.Assertions.html)来自动引入一堆断言宏和函数。 75 | 76 | 在大部分的测试中,你只需要用到的是断言宏是`assert`和`refute`: 77 | 78 | ``` 79 | assert 1 + 1 == 2 80 | refute 1 + 3 == 3 81 | ``` 82 | 83 | 如果测试失败了,ExUnit会自动分解这些表达式,试图提供尽可能详细的信息。例如,这个失败的断言: 84 | 85 | ``` 86 | assert 1 + 1 == 3 87 | ``` 88 | 89 | 以这种形式: 90 | 91 | ``` 92 | Expected 2 to be equal to (==) 3 93 | ``` 94 | 95 | 然而,在一些特殊的例子中,一些另外的断言能让测试变得更容易。一个最好的例子是宏`assert_raise`: 96 | 97 | ``` 98 | assert_raise ArithmeticError, "bad argument in arithmetic expression", fn -> 99 | 1 + "test" 100 | end 101 | ``` 102 | 103 | 所以别忘了查看[ExUnix.Assertions](http://elixir-lang.org/docs/stable/ExUnit.Assertions.html)的文档中的其他例子。 104 | 105 | ## 1.2.3 回调函数 106 | 107 | `ExUnit.Case`定义了四种回调函数:`setup`, `teardown`, `setup_all`和`teardown_all`: 108 | 109 | ``` 110 | defmodule CallbacksTest do 111 | use ExUnit.Case, async: true 112 | 113 | setup do 114 | IO.puts "This is a setup callback" 115 | :ok 116 | end 117 | 118 | test "the truth" do 119 | assert true 120 | end 121 | end 122 | ``` 123 | 124 | 在上面的例子中,回调`setup`将会在每次测试前运行。如果定义了回调`setup_all`,它将在所有的测试之前运行一次。 125 | 126 | 一个回调必须返回`:ok`或`{ :ok, data }`。当返回的是后者的时候,`data`参数必须是一个包含测试元数据的关键字列表。这个元数据可以被其他回调函数或者测试访问到。 127 | 128 | ``` 129 | defmodule CallbacksTest do 130 | use ExUnit.Case, async: true 131 | 132 | setup do 133 | IO.puts "This is a setup callback" 134 | { :ok, from_setup: :hello } 135 | end 136 | 137 | test "the truth", meta do 138 | assert meta[:from_setup] == :hello 139 | end 140 | 141 | teardown meta do 142 | assert meta[:from_setup] == :hello 143 | :ok 144 | end 145 | end 146 | ``` 147 | 148 | 元数据用于需要手动地将状态传递个测试的时候。 149 | 150 | ## 1.2.4 异步 151 | 152 | 最后,``也运行测例并行运行。你只需要把`:async`选项的值设为`true`: 153 | 154 | ``` 155 | use ExUnit.Case, async: true 156 | ``` 157 | 158 | 这将会把这个测试同其他同样是异步的测试,并行运行。其他的测试仍将以顺序的方式运行。 159 | 160 | ## 2 干吧 161 | 162 | ExUnit仍然是一个为完工的项目。我们鼓励你访问我们的[issue tracker](https://github.com/elixir-lang/elixir/issues),提交你希望在ExUnit中看到的新特性, 163 | -------------------------------------------------------------------------------- /getting_started/Chapter13.md: -------------------------------------------------------------------------------- 1 | # 13 `alias`, `require`和`import` 2 | 3 | 为了达成软件的复用性,Elixir提供了三个命令。我将在下面几章看到,它们之所以被称为指令是因为它们有作用域。 4 | 5 | ## 13.1 `alias` 6 | 7 | `alias`允许你给任何模块设置别名。想象一下我们的`Math`模块使用了一个特别的列表实现用于一些特殊的数学计算: 8 | 9 | ``` 10 | defmodule Math do 11 | alias Math.List, as: List 12 | end 13 | ``` 14 | 15 | 那么从现在开始,任何对`List`的引用都会被扩展成`Math.List`。如果你想要使用原版的`List`模块,它还是可以在命名控件`Elixir`里找到: 16 | 17 | ``` 18 | List.flatten #=> uses Math.List.flatten 19 | Elixir.List.flatten #=> uses List.flatten 20 | Elixir.Math.List.flatten #=> uses Math.List.flatten 21 | ``` 22 | 23 | > 注意:Elixir中的所有模块都定义在一个主命名空间`Elixir`下。然而,在引用的时候,你可以忽略它。 24 | 25 | 别名经常被用于定义快捷方式。实际上,不用选项`as`调用`alias`会自动指向模块名的最后一个部分,比如: 26 | 27 | ``` 28 | alias Math.List 29 | ``` 30 | 31 | 等同于: 32 | 33 | ``` 34 | alias Math.List, as: List 35 | ``` 36 | 37 | 注意`alias`是有作用域的,别名只能在某个函数内部起作用: 38 | 39 | ``` 40 | defmodule Math do 41 | def plus(a, b) do 42 | alias Math.List 43 | # ... 44 | end 45 | 46 | def minus(a, b) do 47 | # ... 48 | end 49 | end 50 | ``` 51 | 52 | 在上面的例子中, 因为我们在函数`plus/2`内部启用了`alias`,这个别名只在函数`plus/2`内部有效。`plus/2`则不会被影响。 53 | 54 | ## 13.2 `require` 55 | 56 | Elixir提供了宏作为元编程(用代码产生代码)的机制。 57 | 58 | 宏是在编译时被执行和扩展的代码。这意味着,为了能使用宏,我们需要保证这些模块和实现是在编译时可用的。这可以用`require`来达成: 59 | 60 | ``` 61 | iex> Integer.odd?(3) 62 | ** (CompileError) iex:1: you must require Integer before invoking the macro Integer.odd?/1 63 | iex> require Integer 64 | nil 65 | iex> Integer.odd?(3) 66 | true 67 | ``` 68 | 69 | 在Elixir中,`Integer.odd?/1`是一个可以被当守护使用的宏。这意味着,为了调用`Integer.odd?/1`,我们需要首先引入`Integer`模块。 70 | 71 | 总的来说,一个模块在被使用之前无需被引入,除非我们需要那么模块中的宏。调用一个不可得的宏会导致一个错误。注意和`alias`一样,`require `也是有作用域的。我们将在更详细地讨论宏。 72 | 73 | ## 13.3 `import` 74 | 75 | 用`import`可以非常方面地直接引入其他模块中的函数和宏。例如,如果我们需要反复用到`List`模块中的`duplicate`函数,我们能简单地引入它: 76 | 77 | ``` 78 | iex> import List, only: [duplicate: 2] 79 | nil 80 | iex> duplicate :ok, 3 81 | [:ok, :ok, :ok] 82 | ``` 83 | 84 | 在这个例子中,我们仅仅从`List`里引入了函数`duplicate`(参数量2)。虽然`only:`并非是必须的,还是推荐使用。另一个可选项是`except`。 85 | 86 | `import`也支持`:macros`和`:functions`和`:only`一起使用。例如,要引入所有的宏,可以这么写: 87 | 88 | ``` 89 | import Integer, only: :macros 90 | ``` 91 | 92 | 或这引入全部的函数: 93 | 94 | ``` 95 | import Integer, only: :functions 96 | ``` 97 | 98 | 注意`import`也是有作用域的,这意味着我们能在特定函数内引入特定的宏: 99 | 100 | ``` 101 | defmodule Math do 102 | def some_function do 103 | import List, only: [duplicate: 2] 104 | # call duplicate 105 | end 106 | end 107 | ``` 108 | 109 | 在上面的例子中,引入的`List.duplicate/2`只在那个函数内部才可见。`duplicate/2`对这个模块内的其他函数都是不可用的(更不用说其他的模块了) 110 | 111 | 112 | 注意引入一个模块会自动requires它 113 | 114 | ## 13.4 别名 115 | 116 | 到这李,也许你在好奇Elixir中的别名到底是什么?它们究竟是如何存在的? 117 | 118 | Elixir中的别名首先是一个首字母大写的识别符(像`String`,`Keyword`之类),会在编译时转换成原子。例如,别名`String`默认翻译成`:"Elixir.String"`: 119 | 120 | ``` 121 | iex> is_atom(String) 122 | true 123 | iex> to_string(String) 124 | "Elixir.String" 125 | iex> :"Elixir.String" 126 | String 127 | ``` 128 | 129 | 使用了`alias/2`,我们就能改变了一个别名最后翻译的结果。 130 | 131 | 之所以这样是因为在Erlang虚拟机(随之在Elixir)中,模块是用原子表现的。例如,下面是我们如何效用Erlang的模块的: 132 | 133 | ``` 134 | iex> :lists.flatten([1,[2],3]) 135 | [1, 2, 3] 136 | ``` 137 | 138 | 这套机制同时也允许我们动态地代用模块内的一个函数: 139 | 140 | ``` 141 | iex> mod = :lists 142 | :lists 143 | iex> mod.flatten([1,[2],3]) 144 | [1,2,3] 145 | ``` 146 | 147 | 也就是说,我们直接在原子`:lists`上调用函数`flatten`。 148 | 149 | ## 13.5 嵌套 150 | 151 | 讨论完了别名,我们可以接着谈谈嵌套,以及它是如何工作的。考虑一下下面的代码: 152 | 153 | ``` 154 | defmodule Foo do 155 | defmodule Bar do 156 | end 157 | end 158 | ``` 159 | 160 | 上面的这个例子定义了两个模块`Foo`和`Foo.Bar`。只要它们一直在同一个作用域内,第二个模块都可以作为`Foo`内部的`Bar`被访问到。如果之后开发者决定把`Bar`移到另一个文件,引用它就需要全名(`Foo.Bar`)了,要么像我们之前讨论的一样,用`alias`设置一个别名。 161 | 162 | 也就是说,上面的代码等价于: 163 | 164 | ``` 165 | defmodule Elixir.Foo do 166 | defmodule Elixir.Foo.Bar do 167 | end 168 | alias Elixir.Foo.Bar, as: Bar 169 | end 170 | ``` 171 | 172 | 随后几章我们将看到,别名也在宏里扮演了关键的角色,用来保证它们的同构性。到这里我们差不多完成了Elixir中的模块部分,下一章是关于模块的属性的。 173 | -------------------------------------------------------------------------------- /mix/Chapter3.md: -------------------------------------------------------------------------------- 1 | # 3 定制Mix任务 2 | 3 | * [3.1 通用API](#toc_1) 4 | * [3.2 命名空间任务](#toc_2) 5 | * [OptionParser](#toc_3) 6 | * [3.4 共享任务](#toc_4) 7 | * [3.4.1 作为一个依赖](#toc_5) 8 | * [3.4.2 作为一个存档](#toc_6) 9 | * [3.4.3 MIX_PATH](#toc_7) 10 | 11 | 在Mix里,一个任务只不过是一个包含`run/1`函数,`Mix.Tasks`命名空间下的Elixir模块而已。例如,任务`compile`其实是一个名为`Mix.Tasks.Compile`的模块。 12 | 13 | 让我们创建一个简单的模块: 14 | 15 | ``` 16 | defmodule Mix.Tasks.Hello do 17 | use Mix.Task 18 | 19 | @shortdoc "This is short documentation, see" 20 | 21 | @moduledoc """ 22 | A test task. 23 | """ 24 | def run(_) do 25 | IO.puts "Hello, World!" 26 | end 27 | end 28 | ``` 29 | 30 | 把这段代码保存到文件`hello.ex`里,并编译和运行: 31 | 32 | ``` 33 | $ elixirc hello.ex 34 | $ mix hello 35 | Hello, World! 36 | ``` 37 | 38 | 上面的模块定义了一个名为`hello`的任何。函数`run/1`接受一个类型是二进制字符串列表的参数,它就是通过命令行传递过来的参数。 39 | 40 | 当你调用`mix hello`, 这个任务被运行并打印`Hello, World!`。Mix使用它的第一个参数(在这里是`hello`),来查找任务模块,一旦找到就执行其中的`run`函数。 41 | 42 | 你也许会好奇为何我们有一个`@moduledoc`和`@shortdoc`。这两者都被`help`任务用来表列任务和提供文档。前者被用于当`mix help TASK`的时候,而后者在总体表列`mix help`的时候使用。 43 | 44 | 除了这两,还有一个``属性,当被设成`true`的时候,表明这个任务是隐藏的,不会被显示在`mix help`里。任何没有`@shortdoc`的任务也不会被显示。 45 | 46 | # 3.1 通用API 47 | 48 | 当编写任务的时候,有一些常用的Mix函数可以使用。它们是: 49 | 50 | * `Mix.Project.config` - 返回函数`project`中定义的项目的配置;注意,如果项目中没有文件`mix.exs`,这个函数返回一个空配置。这运行众多的Mix函数即时没有`mix.exs`项目也能工作。 51 | * `Mix.Project.get!` - 访问当前项目的模块,这可以让你访问项目中的特殊函数。如果项目不存在,将导致一个错误。 52 | * `Mix.shell` - 这个壳在Mix做IO的简单抽象。这个抽象使得比较容易地测试现存的Mix任务。在未来,这个壳还将轻松地提供彩色输出和得到用户输入。 53 | * `Mix.Task.run(task, args) ` - 这是你如何从一个任务中调用另一个Mix任务。注意如果任务已经被调用,it works as no-op; 54 | 55 | 更多的有关MixApi的文档,请访问[这里](http://elixir-lang.org/docs/stable/Mix.html),特别注意其中的[`Mix.Task`](http://elixir-lang.org/docs/stable/Mix.Task.html)和[`Mix.Project`](http://elixir-lang.org/docs/stable/Mix.Project.html)。 56 | 57 | # 3.2 命名空间任务 58 | 59 | 虽然任务是非常简单的,但它们能被用于完成复杂的事情。因为它们仅仅是Elixir代码,任何你能用普通Elixir代码做的事情,也能用于Mix任务。你能和普通代码库一样发布任务,也就是说它们能在别的项目中被复用。 60 | 61 | 所以,当你有一堆的相关的任务时该怎么办?如果你按照`foo`,`bar,`baz`的方式命名它们,很快你就会很其他人的任务冲突了。为了避免这样的情况,Mix运行你对任务使用命名空间。 62 | 63 | 让我们假设你有一堆用于Riak的任务: 64 | 65 | ``` 66 | defmodule Mix.Tasks.Riak do 67 | defmodule Dostuff do 68 | ... 69 | end 70 | 71 | defmodule Dootherstuff do 72 | ... 73 | end 74 | end 75 | ``` 76 | 77 | 现在你在模块`Mix.Tasks.Riak.Dostuff`和`Mix.Tasks.Riak.Dootherstuff`下有了两个不同的任务。你可以想遮阳调用这些函数:`mix riak.dostuff`和`riak.dootherstuff`。很酷,不是吗? 78 | 79 | 当你有一堆相互关联的任务,而对它们单独命名有会非常不方便时使用这个特性。如果你只有不多的几个任务,那就随你怎么处理了。 80 | 81 | # 3.3 OptionParser 82 | 83 | 虽然并非是一个Mix的特性,Elixir随带了一个`OptionParser`,当用于创建Mix任务和处理接受到选项是比较有用。`OptionParser`接受一个列表作为参数,返回一个包含经过处理的选项和未能被处理的参数: 84 | 85 | ``` 86 | OptionParser.parse(["--debug"]) 87 | #=> { [debug: true], [] } 88 | 89 | OptionParser.parse(["--source", "lib"]) 90 | #=> { [source: "lib"], [] } 91 | 92 | OptionParser.parse(["--source", "lib", "test/enum_test.exs", "--verbose"]) 93 | #=> { [source: "lib", verbose: true], ["test/enum_test.exs"] } 94 | ``` 95 | 96 | 访问``文档来获得跟多信息。 97 | 98 | # 3.4 共享任务 99 | 100 | 当你创建了自己的任务,你也许会希望能同别的开发者分享或在现有的项目就中复用。在这一部分,我们将看到在Mix中不同的分享的办法。 101 | 102 | # 3.4.1 作为一个依赖 103 | 104 | 想象一已经创建了一个Mix项目,`my_tasks`, 提供了一些任务。把`my_tasks`作为一个依赖加入到其他项目中,`my_tasks`中的所有任务都将可以父项目中使用。就这么简单! 105 | 106 | # 3.4.2 作为一个存档 107 | 108 | Mix任务不仅在项目内有用,而且能被用于创建新项目,自动化复杂任务和避免重复劳动。在这些场景中,你会希望一个任务始终能在工作流中被使用,无论是否在项目中。 109 | 110 | 针对这些需求,Mix允许开发者在本地安装和卸载存档。要从当前项目产生一个存档并在本地安装,你可以运行: 111 | 112 | ``` 113 | $ mix do archive, local.install 114 | ``` 115 | 116 | 存档能从一个路径或者其他任何URL安装: 117 | 118 | ``` 119 | $ mix local.install http://example.org/path/to/sample/archive.ez 120 | ``` 121 | 122 | 存档安装完之后,你就可以运行其中的所有任务了,用`mix local`来表列它们,或用`mix local.uninstall archive.ez`来卸载包裹。 123 | 124 | # 3.4.3 MIX_PATH 125 | 126 | 最后一个用于分享的机制是`MIX_PATH`。通过设置你的`MIX_PATH`, 任何在`MIX_PATH`路径内的任务都能自动被Mix可见。这里是一个例子: 127 | 128 | ``` 129 | $ export MIX_PATH="/full/path/to/my/project/ebin" 130 | ``` 131 | 132 | 这对那些必须安装在`/usr`和`/opt`目录下的,但有希望仍然能和Mix绑定的复杂项目比较有用。 133 | 134 | 学习了这些选项,你就可以出师, 创建并安装你自己的任务了!加油! 135 | -------------------------------------------------------------------------------- /getting_started/Chapter19.md: -------------------------------------------------------------------------------- 1 | # 19 Sigils 2 | 3 | 我们已经学习到了Elixir提供了双引号的字符串和单引号的字符列表。然而,这只是这个语言中那些具有文字表现形式的结构的表层现象。比如,原子,是另一种常常通过``形式创建的结构。 4 | 5 | Elixir的其中一个目标是可扩展性:开发者应该能够在某个领域扩展语言。计算机科学已经变得如此宽广的范围,以至于已经不可能仅仅用一种语言的核心就能解决所有的问题。我们最佳的赌注是使得语言可扩展,那样开发者,公司或者社区能在相关的领域内扩展语言。 6 | 7 | 在这一章,我们将探索sigil,它是语言层面提供的能用于字面呈现的机制之一。 8 | 9 | # 19.1 正则表达式 10 | 11 | Sigils由一个约等于号(`~`)开始,跟着一个字母,再跟着是一个分隔符。最常见的sigil是`~r`,用于[正则表达式](https://en.wikipedia.org/wiki/Regular_Expressions): 12 | 13 | ``` 14 | # A regular expression that returns true if the text has foo or bar 15 | iex> regex = ~r/foo|bar/ 16 | ~r/foo|bar/ 17 | iex> "foo" =~ regex 18 | true 19 | iex> "bat" =~ regex 20 | false 21 | ``` 22 | 23 | Elixir提供了Perl兼容的正则表达式(regrexs),类似于在[PCRE](http://www.pcre.org/)库中实现的。正则也支持修饰符。例如,修饰符`i`会使得一个正则表达式无关大小写: 24 | 25 | ``` 26 | iex> "HELLO" =~ ~r/hello/ 27 | false 28 | iex> "HELLO" =~ ~r/hello/i 29 | true 30 | ``` 31 | 32 | 在[`Regex`](http://elixir-lang.org/docs/stable/Regex.html)模块的文档中有更详细的有关正则表达式的其他修饰符和所支持的其他操作的内容。 33 | 34 | 到目前为止,所有的例子都使用了`/`来分割正则表达式。然而sigil支持8中不同的分隔符: 35 | 36 | ``` 37 | ~r/hello/ 38 | ~r|hello| 39 | ~r"hello" 40 | ~r'hello' 41 | ~r(hello) 42 | ~r[hello] 43 | ~r{hello} 44 | ~r 45 | ``` 46 | 47 | 只所以支持这么多不同的操作符是因为不同的分隔符能更方便地在不同的sigils中使用。例如,在正则中使用圆括号也许就不合适,因为这会和正则内部的圆括号混淆。然而, 圆括号在其他的sigil中就非常适用,我们将在下面看到例证。 48 | 49 | ## 19.2 字符串,字符列表和其他sigils 50 | 51 | 除了正则,Elixir默认提供了其他三中sigils。 52 | 53 | `~s`sigils被用于产生字符串,同双引号类似。 54 | 55 | ``` 56 | iex> ~s(this is a string with "quotes") 57 | "this is a string with \"quotes\"" 58 | ``` 59 | 60 | 而`~c`sigil用于产生字符列表: 61 | 62 | ``` 63 | iex> ~c(this is a string with "quotes") 64 | 'this is a string with "quotes"' 65 | ``` 66 | 67 | `~w`sigil用于产生一个用空格分割的词汇的列表: 68 | 69 | ``` 70 | iex> ~w(foo bar bat) 71 | ["foo", "bar", "bat"] 72 | ``` 73 | 74 | `~w`sigil也能接受修饰符`c`,`s`和`a`,来选择结果的格式: 75 | 76 | ``` 77 | iex> ~w(foo bar bat)a 78 | [:foo, :bar, :bat] 79 | ``` 80 | 81 | 除了小写字母的sigils, Elixir也支持大写形式的sigils。虽然`~s`和`~S`都返回字符串,前者能允许代码忽略和融合,而后者不能: 82 | 83 | 84 | ``` 85 | iex> ~s(String with escape codes \x26 interpolation) 86 | "String with escape codes & interpolation" 87 | iex> ~S(String without escape codes and without #{interpolation}) 88 | "String without escape codes and without \#{interpolation}" 89 | ``` 90 | 91 | 下面这些忽略符号能被用于字符串和字符列表: 92 | * `\"` - 双引号 93 | * `\''` - 单引号 94 | * `\\` - 单斜杠 95 | * `\a` - bell/alert 96 | * `\b` - backspace 97 | * `\d` - 删除 98 | * `\e` - 忽略 99 | * `\f` - form feed 100 | * `\n` - 换行符 101 | * `\r` - 回车 102 | * `\s` - 空格 103 | * `\t` - tab 104 | * `\v` - vertial tab 105 | * `\DDD`,`\DD`,`\D` - 用八进制表示的字符 DDD,DD或D(例如`\377`) 106 | * `\xDD` - 用十六进制表示的字符 DD, 例如(`\x37`) 107 | * `\x{D...}` - 用十六禁止表示的字符,至少含有一个十六进制字符(例如, `/x{abc123}`) 108 | 109 | Sigil也支持heredocs,用三双引号或三单引号来作为分隔符: 110 | 111 | ``` 112 | iex> ~s""" 113 | ...> this is 114 | ...> a heredoc string 115 | ...> """ 116 | ``` 117 | 118 | 最常见的heredocs sigil是用于编写文档。例如,如果你需要在你的文档中包含忽略字符,会很麻烦,因为它需要对某些字符做双重忽略: 119 | 120 | ``` 121 | @doc """ 122 | Converts double-quotes to single-quotes. 123 | 124 | ## Examples 125 | 126 | iex> convert("\\\"foo\\\"") 127 | "'foo'" 128 | 129 | """ 130 | def convert(...) 131 | ``` 132 | 133 | 但有了`-S`, 我们能彻底避免这个问题: 134 | 135 | ``` 136 | @doc ~S""" 137 | Converts double-quotes to single-quotes. 138 | 139 | ## Examples 140 | 141 | iex> convert("\"foo\"") 142 | "'foo'" 143 | 144 | """ 145 | def convert(...) 146 | ``` 147 | 148 | # 19.3 定制sigil 149 | 150 | 正如在开头暗示的那样,sigil是Elixir中可扩展的。实际上,sigil`sigil_r`相当于用两个参数调用函数`sigil_r`: 151 | 152 | ``` 153 | iex> sigil_r(<<"foo">>, 'i') 154 | ~r"foo"i 155 | ``` 156 | 157 | 这就是说,我们通过函数`sigil_r`来得到sigil`~r`的文档: 158 | 159 | 160 | ``` 161 | iex> h sigil_r 162 | ... 163 | ``` 164 | 165 | 我们也能通过实现何时的函数,我们也能实现自己的sigil。例如,让我们实现sigil`~i(13)`来回返一个整数: 166 | 167 | ``` 168 | iex> defmodule MySigils do 169 | ...> def sigil_i(binary, []), do: binary_to_integer(binary) 170 | ...> end 171 | iex> import MySigils 172 | iex> ~i(13) 173 | 13 174 | ``` 175 | 176 | sigil也能在宏的帮助下,在编译时发挥作用。例如,在Elixir中正则表达式在编译时会被编译成高效的代码形式,随后就无需在运行时做这些事情了。如果你对这个主题感兴趣,我们推荐你学习宏并且查阅这些sigils是如何在`Kernel`模块中实现的。 177 | -------------------------------------------------------------------------------- /getting_started/Chapter14.md: -------------------------------------------------------------------------------- 1 | # 14 模块属性 2 | 3 | Elixir中的模块属性有三个作用: 4 | 5 | 1. 它们提供对模块的注释, 很多信息对用户和Erlang虚拟机都有用。 6 | 7 | 2. 它们也是恒量。 8 | 9 | 3. 在编译时,它们也是模块的零时存储。 10 | 11 | 让我们一一来看看 12 | 13 | ## 14.1 注释 14 | 15 | Elixir从Erlang中借用了模块属性的概念。例如 16 | 17 | ``` 18 | defmodule MyServer do 19 | @vsn 2 20 | end 21 | ``` 22 | 在上面的例子中,我们显明地设置了那个模块的版本。Erlang的代码重载机制使用`@vsn`来检查一个模块是否已经是最新的。如果版本没有指定,版本就是模块函数的md5数值。 23 | 24 | Elixir有一些保留的属性。下面这些只是其中最常用的部分: 25 | 26 | * `@moduledoc` 提供当前模块的文档 27 | * `@doc` 提供紧跟这个属性的函数或宏的文档 28 | * `@behaviour` (注意英式英语拼法)用来指定一个OTP或用户指定的行为 29 | * `@before_compile` 提供了一个在模块被编译之前的调用的hook。我们可以利用它在模块编译之前注入新的函数。 30 | 31 | `@moduledoc`和`@doc`是最产用的两个属性,我们会经常用到。Elixir把文档当成第一等公民,提供了很多用于访问文档的函数: 32 | 33 | ``` 34 | iex> defmodule MyModule do 35 | ...> @moduledoc "It does **x**" 36 | ...> 37 | ...> @doc """ 38 | ...> Returns the version 39 | ...> """ 40 | ...> def version, do: 1 41 | ...> end 42 | {:module, MyModule, <<70, 79, 82, ...>>, {:version, 0}} 43 | iex> h MyModule 44 | * MyModule 45 | 46 | It does **x** 47 | 48 | iex> h MyModule.version 49 | * def version() 50 | 51 | Returns the version 52 | ``` 53 | 54 | Elixir的惯例是用markdown和heredocs来编写注重可读性的文档。Heredocs是多行的字符串,它们以三联的引号结束,这样就不用破坏内部的格式了: 55 | 56 | ``` 57 | defmodule Math do 58 | @moduledoc """ 59 | This module provides mathematical functions 60 | as sin, cos and constants like pi. 61 | 62 | ## Examples 63 | 64 | Math.pi 65 | #=> 3.1415... 66 | 67 | """ 68 | end 69 | ``` 70 | 71 | 我们也提供了一个叫[ExDoc](https://github.com/elixir-lang/ex_doc)的工具,它能被用于从文档里产生HTML网页。 72 | 73 | 你可以通过查看[Module](http://elixir-lang.org/docs/stable/Module.html)的文档来找到支持的属性的完整列表。Elixir也用属性来定义[typespecs](http://elixir-lang.org/docs/stable/Kernel.Typespec.html),通过: 74 | 75 | * `@spec` - 提供了函数的指标 76 | * `@callback` - 提供了行为回调函数的指标 77 | * `@type` - 定义了在`@spec`内使用的类型 78 | * `@typep` - 定义了在`@spec`内部使用的私有类型 79 | * `@opaque` - 定义了在`@spec`内部使用的opaque类型 80 | 81 | 这个部分涵盖了内建的属性。然而,属性也能被开发者使用或被库扩展来支持客制化的行为。 82 | 83 | ## 14.2 作为恒量 84 | 85 | Elixir开发者会经常用模块属性来当成恒量: 86 | 87 | ``` 88 | defmodule MyServer do 89 | @initial_state %{host: "147.0.0.1", port: 3456} 90 | IO.inspect @initial_state 91 | end 92 | ``` 93 | 94 | > 注意,和Erlang不同,用户定义的属性默认不被存储咋模块内。它们的值只有在编译时才存在。开发者能用通过调用[`Module.register_attribute/3`](http://elixir-lang.org/docs/stable/Module.html#register_attribute/3)来使得一个属性接近Erlang中的行为。 95 | 96 | 试图去访问一个不存在的属性为导致一个警告: 97 | 98 | ``` 99 | defmodule MyServer do 100 | @unknown 101 | end 102 | warning: undefined module attribute @unknown, please remove access to @unknown or explicitly set it to nil before access 103 | ``` 104 | 105 | 最后,属性能被从函数内部访问: 106 | 107 | ``` 108 | defmodule MyServer do 109 | @my_data 14 110 | def first_data, do: @my_data 111 | @my_data 13 112 | def second_data, do: @my_data 113 | end 114 | 115 | MyServer.first_data #=> 14 116 | MyServer.second_data #=> 13 117 | ``` 118 | 119 | 注意从函数内部读取一个属性的值,只是这个值的当前切片。也就是说,读取是在编译时而不是运行时发生的。我们将看到,这让属性能被当作编译时的溢恶存储机制。 120 | 121 | ## 14.3 作为零时存储 122 | 123 | Elixir组织其中的一个项目是[`Plug`](https://github.com/elixir-lang/plug),它是目标是称为Elixir中编写web库和框架的通过基础。 124 | 125 | Plub库也允许开发者去定义它们自己的plugs,能被在web服务器内运行: 126 | 127 | ``` 128 | defmodule MyPlug do 129 | use Plug.Builder 130 | 131 | plug :set_header 132 | plug :send_ok 133 | 134 | def set_header(conn, _opts) do 135 | put_resp_header(conn, "x-header", "set") 136 | end 137 | 138 | def send_ok(conn, _opts) do 139 | send(conn, 200, "ok") 140 | end 141 | end 142 | 143 | IO.puts "Running MyPlug with Cowboy on http://localhost:4000" 144 | Plug.Adapters.Cowboy.http MyPlug, [] 145 | ``` 146 | 147 | 在上面的例子中,我们用宏` plug/1`去连接那些会被当web服务器存在时调用的函数。从内部来讲,每次你调用`plug/1`, Plub库会把接受到的参数存储在属性`@plugs`内。就在模块编译之气那,Plug调用了一个定义了访法(`call/2`)的回调,用它来处理请求。这个访法将按次序运行属性`@plugs`中的所有plugs。 148 | 149 | 为了能理解底层的代码,我们需要宏,随意我们讲在有关元编程的那一章再次研究这个模式。然而当前的焦点是去用模块属性来当成一个存储来运行开发者来创建DSL。 150 | 151 | 另一个例子来自于ExUnit框架,它用模块属性来注释和存储。 152 | 153 | ``` 154 | defmodule MyTest do 155 | use ExUnit.Case 156 | 157 | @tag :external 158 | test "contacts external service" do 159 | # ... 160 | end 161 | end 162 | ``` 163 | 164 | ExUnit中的标签用来注释测试,之后也能被用于过滤测试。例如,你能避免运行外部测试在你的机器上,因为它们都很慢而且依赖于其他的服务,当然你也可以在你自己的编译系统上开启它们。 165 | 166 | 我们希望这个部分让你领略了Elixir是如何支持元编程和模块属性是如何在其中扮演了一个重要的角色。 167 | 168 | 在涉及到异常处理和其他结构sigils和comprehensions之气那,我们将在下一章,探索structs和协议。 169 | -------------------------------------------------------------------------------- /getting_started/Chapter6.md: -------------------------------------------------------------------------------- 1 | # 二进制,字符串和字符列表 2 | 3 | 在“基础数据类型”一章,我们学习了关于字符串以及如何用函数`is_binary/1`作检查: 4 | 5 | ``` 6 | iex> string = "hello" 7 | "hello" 8 | iex> is_binary string 9 | true 10 | ``` 11 | 12 | 在这章,我们将理解什么是二进制,它们和字符串的关系以及一个单引号的值,比如`like this`在Elixi中的含义。 13 | 14 | ## 6.1 UTF-8和Unicode 15 | 16 | 字符串是用UTF-8编码的二进制。如果要彻底地理解这句话的意思,我们需要理解字节和codepoint之间的不同。 17 | 18 | Unicode给每个我们熟知的字符都赋予了一个codepoint。例如,字母`"a"`的codepoint是`97`而字母`"ł"`的codepoint是322.当把字符串`"hełło"`写道磁盘上的时候,我们需要把这些codepoint转换为字节。我们假定一个字节对应一个codepoint,那我们就没法写`"hełło"`了,因为`"ł"`的codepoint是`322`,而一个字节只能表`0`到`255`.不过当然,既然你可以读取字符串`"hełło"`在你的屏幕上,它一定用了某种方法。这种方法就是编码(encoding)。 19 | 20 | 当用字节代表codepoint,我们需要用某种方法将之编码。Elixir选择UTF-8编码作为自己的默认编码。当我们说字符串是用UTF-8的字符串,是指字符串是按照UTF-8标准将字节组织起来用以表示codepoing的。 21 | 22 | 既然我们有字符`"ł"`这样的`322`的codepoint,我们需要一个以上的字节来表示。这也是为什么我们看到计算字符串的字节大小和字符的长度的结果式不同的: 23 | 24 | ``` 25 | iex> string = "hełło" 26 | "hełło" 27 | iex> byte_size string 28 | 7 29 | iex> String.length string 30 | 5 31 | ``` 32 | 33 | UTF-8能用一个字节表示codepoint`h`,`e`和`o`,但两个字节才能表示``。在Elixir里,有可以用`?`得到字符的codepoint: 34 | 35 | ``` 36 | iex> ?a 37 | 97 38 | iex> ?ł 39 | 322 40 | ``` 41 | 42 | 你也可以用[字符串模块](http://elixir-lang.org/docs/stable/String.htm)l中的函数把字符串分解成codepoint: 43 | 44 | ``` 45 | iex> String.codepoints("hełło") 46 | ["h", "e", "ł", "ł", "o"] 47 | ``` 48 | 49 | 你将会看到Elixir对字符串有着极好的支持。它也支持许多的Unicode操作。事实上,Elixir通过了[“The string type is broken”](http://mortoray.com/2013/11/27/the-string-type-is-broken/)中的所有测试。 50 | 51 | 然而,字符串仅仅式故事的一部分。如果字符串是一串二进制,而且我们可以用函数``,那Elixir必定在底层有某种数据类型在支撑字符串。事实就是如此,让我们谈谈二进制吧! 52 | 53 | ## 6.2 二进制(和比特串) 54 | 55 | 在Elixir里,你可以用`<<>>`定义二进制: 56 | 57 | ``` 58 | iex> <<0, 1, 2, 3>> 59 | <<0, 1, 2, 3>> 60 | iex> byte_size <<0, 1, 2, 3>> 61 | 4 62 | ``` 63 | 64 | 一个二进制仅仅式一个字节序列。当然,这些字节可以用任何方式解释,甚至式非法的字符串: 65 | 66 | ``` 67 | iex> String.valid?(<<239, 191, 191>>) 68 | false 69 | ``` 70 | 71 | 字符串合并操作实际上是一个二进制合并操作符: 72 | 73 | ``` 74 | iex> <<0, 1>> <> <<2, 3>> 75 | <<0, 1, 2, 3>> 76 | ``` 77 | 78 | 一个Elixir中常用的敲门就是用空二进制`<<0>>`同一个字符串合并来获取字符串的内部表示: 79 | 80 | ``` 81 | iex> "hełło" <> <<0>> 82 | <<104, 101, 197, 130, 197, 130, 111, 0>> 83 | ``` 84 | 85 | 二进制中的每个数字代表一个比特,所以最大值只能是255。但同时二进制也可以通过添加新修改符(modifier)来表示大于255的数字或者把一个codepoint转换成对应的UTF-8表示: 86 | 87 | ``` 88 | iex> <<255>> 89 | <<255>> 90 | iex> <<256>> # truncated 91 | <<0>> 92 | iex> <<256 :: size(16)>> # use 16 bits (2 bytes) to store the number 93 | <<1, 0>> 94 | iex> <<256 :: utf8>> # the number is a codepoint 95 | "Ā" 96 | iex> <<256 :: utf8, 0>> 97 | <<196, 128, 0>> 98 | ``` 99 | 100 | 既然一个字节有8比特,如果我们把大小设为1个比特,会怎么样? 101 | 102 | ``` 103 | iex> <<1 :: size(1)>> 104 | <<1 :: size(1)>> 105 | iex> <<2 :: size(1)>> # truncated 106 | <<0>> 107 | iex> is_binary(<< 1 :: size(1)>>) 108 | false 109 | iex> is_bitstring(<< 1 :: size(1)>>) 110 | true 111 | iex> bit_size(<< 1 :: size(1)>>) 112 | 1 113 | ``` 114 | 115 | 这样的话它的值就不再是二进制,而是一个比特串(bitstring)了 -- 只不过式一堆的比特。所以二进制只不过是长度除以8的比特串而已! 116 | 117 | 我们也可以对二进制和比特串进行模式匹配: 118 | 119 | ``` 120 | iex> <<0, 1, x>> = <<0, 1, 2>> 121 | <<0, 1, 2>> 122 | iex> x 123 | 2 124 | iex> <<0, 1, x>> = <<0, 1, 2, 3>> 125 | ** (MatchError) no match of right hand side value: <<0, 1, 2, 3>> 126 | ``` 127 | 128 | 注意上面的每一个二进制都要求匹配正好8比特。然而,我们也可以用修改符来匹配剩余的二进制: 129 | 130 | ``` 131 | iex> <<0, 1, x :: binary>> = <<0, 1, 2, 3>> 132 | <<0, 1, 2, 3>> 133 | iex> x 134 | <<2, 3>> 135 | ``` 136 | 137 | 上面的模式仅仅在二进制在`<<>>`尾部的情况下才有效。相似的情况也出现在字符串合并操作符`<>`上: 138 | 139 | ``` 140 | iex> "he" <> rest = "hello" 141 | "hello" 142 | iex> rest 143 | "llo" 144 | ``` 145 | 146 | 到这里,关于比特串,二进制和字符串就结束了。一个字符串是UTF-8编码的二进制,二进制是长度除以8的比特串。虽然这显示了Elixir在如何处理bite和字节上的灵活性,但在99%的情况下你只会和二进制, 以及函数`is_binary/1`和`byte_size/1`打交道。 147 | 148 | ## 6.3 字符列表 149 | 150 | 一个字符列表只不过是一个字符的列表: 151 | 152 | ``` 153 | iex> 'hełło' 154 | [104, 101, 322, 322, 111] 155 | iex> is_list 'hełło' 156 | ``` 157 | 158 | 你可以看到,它没有用字节而是用了包含单引号之间的字符的codepoint的列表。所以,双引号表示的是字符串(二进制),单引号表示的式字符列表(列表)。 159 | 160 | 在实际中,字符列表最常见的引用是在和Erlang代码的时候,特别式一些比较老的库没法用接受二进制的参数。你能用函数`to_string/1`和`to_char_list/1`,在字符列表和二进制之间互相转换。 161 | 162 | ``` 163 | iex> to_char_list "hełło" 164 | [104, 101, 322, 322, 111] 165 | iex> to_string 'hełło' 166 | "hełło" 167 | iex> to_string :hello 168 | "hello" 169 | iex> to_string 1 170 | "1" 171 | ``` 172 | 173 | 注意这些函数式多态的。它们不仅仅能把字符列表转成字符串,而且也能转整数和原子等其他类型。 174 | 175 | 有了二进制,字符串和字符列表,是时候谈一下键-值对数据结构了。 176 | -------------------------------------------------------------------------------- /getting_started/Chapter17.md: -------------------------------------------------------------------------------- 1 | # 17 `try`, `catch`和`rescue` 2 | 3 | Elixir有三个错误处理机制:错误,抛出和退出。在这一章我们将一一探索它们,包括应该在什么时候使用它们。 4 | 5 | ## 17.1 错误 6 | 7 | 第一个典型的错误是试图把一个数字和原子相加: 8 | 9 | ``` 10 | iex> :foo + 1 11 | ** (ArithmeticError) bad argument in arithmetic expression 12 | :erlang.+(:foo, 1) 13 | ``` 14 | 15 | 在运行时调用宏`raise/1 `导致一个错误: 16 | 17 | ``` 18 | iex> raise "oops" 19 | ** (RuntimeError) oops 20 | ``` 21 | 22 | 另一些错误能通过给`raise/2`传递一个错误名和一个关键字列表来形成: 23 | 24 | ``` 25 | iex> raise ArgumentError, message: "invalid argument foo" 26 | ** (ArgumentError) invalid argument foo 27 | ``` 28 | 29 | 你也可以用宏`defexception/2`定义你自己的错误。最常见的例子是定义一个带有message域的异常: 30 | 31 | ``` 32 | iex> defexception MyError, message: "default message" 33 | iex> raise MyError 34 | ** (MyError) default message 35 | iex> raise MyError, message: "custom message" 36 | ** (MyError) custom message 37 | ``` 38 | 39 | 异常可以被通过`try/rescue`挽救: 40 | 41 | ``` 42 | iex> try do 43 | ...> raise "oops" 44 | ...> rescue 45 | ...> e in RuntimeError -> e 46 | ...> end 47 | RuntimeError[message: "oops"] 48 | ``` 49 | 50 | 上面的例子挽救了一个运行时错误并返回这个错误,并在`iex`的session中打印出来。在实践中Elixir开发者很少使用`try/rescue`结构。例如,许多语言中但一个文件无法被打开时,会强制你去挽救一个错误。相反Elixir提供了一个函数`File.read/1`,它返回一个包含文件是否被成功打开的相关信息的元组。 51 | 52 | ``` 53 | iex> File.read "hello" 54 | {:error, :enoent} 55 | iex. File.write "hello", "world" 56 | :ok 57 | iex> File.read "hello" 58 | {:ok, "world"} 59 | ``` 60 | 61 | 这里没有`try/rescue`。如果你想要处理开打文件的不同后果,你可以非常方便地使用`case`做模式识别: 62 | 63 | ``` 64 | iex> case File.read "hello" do 65 | ...> {:ok, body} -> IO.puts "got ok" 66 | ...> {:error, body} -> IO.puts "got error" 67 | ...> end 68 | ``` 69 | 70 | 当然,最终还是取决你自己的应用来决定打开一个文件是不是一个错误。这也是为什么Elixir没有在`File.read/1`和其他函数中只用异常。它把选择最佳的处理方式的决定留给了开发者。 71 | 72 | 在某些情况下,当你期待一个文件的确存在(并如果没有这个文件,这的确是一个错误),有可以轻松地嗲用`File.read!/1`: 73 | 74 | ``` 75 | iex> File.read! "unknown" 76 | ** (File.Error) could not read file unknown: no such file or directory 77 | (elixir) lib/file.ex:305: File.read!/1 78 | ``` 79 | 80 | 用另一种话来说,我们避免使用`try/rescue`因为我们不用错误来做控制流程。在Eliixr中,我们对错误的理解是字面意义上的:它们就是没有预期的或留给意外的或情况的。如果你的确需要流程控制结构,就需要用到throw。这也是下一章的内容。 81 | 82 | ## 17.2 抛出 83 | 84 | 在Elixir中,一个抛出的值能之后被捕获。`throw`和`catch`是用于除了是用`throw`和`catch`之外没法获取一个值的情况。 85 | 86 | 这些情况在时间中是不常见的,除非当你要和一些API定义地不好的库打交道的时候。例如,让我们想象`v`模块没有提供提供寻找值的API,所以我们必须去找到第一个是13的倍数的数字: 87 | 88 | ``` 89 | iex> try do 90 | ...> Enum.each -50..50, fn(x) -> 91 | ...> if rem(x, 13) == 0, do: throw(x) 92 | ...> end 93 | ...> "Got nothing" 94 | ...> catch 95 | ...> x -> "Got #{x}" 96 | ...> end 97 | "Got -39" 98 | ``` 99 | 100 | 然而,在实际上`Enum.find/2`就可以轻松做到: 101 | 102 | ``` 103 | iex> Enum.find -50..50, &(rem(&1, 13) == 0) 104 | -39 105 | ``` 106 | 107 | ## 17.3 退出 108 | 109 | 所有的Eliixr代码都运行在进程中,进程之间互相通信。当一个进程死亡,它会发送一个`exit`信号。也可以手动发送一个退出信号来杀死一个进程: 110 | 111 | ``` 112 | iex> spawn_link fn -> exit(1) end 113 | #PID<0.56.0> 114 | ** (EXIT from #PID<0.56.0>) 1 115 | ``` 116 | 117 | 在上面的例子中,我们链接了死亡的进程之后发送了退出信号,它的值是1.Elixir控制台自动处理这些消息,并把它们打印出来: 118 | 119 | `exit`也能被``捕获: 120 | 121 | ``` 122 | iex> try do 123 | ...> exit "I am exiting" 124 | ...> catch 125 | ...> :exit, _ -> "not really" 126 | ...> end 127 | "not really" 128 | ``` 129 | 130 | 用`try/catch`已经是非常罕见的,用它们来捕获退出更是少见。 131 | 132 | `exit`信号是Eralng虚拟机提供的tolerant机制的重要组成部分。进程通常在监控树之下运行,它们其实是一个等待被监控的进程的退出信号的进程。当一个收到一个退出信号,它们的监控策略被触发并将死亡的被监控进程重启。 133 | 134 | 正式这个监控系统使得`try/catch`和`try/rescue`在Elixir中用的这么少。与其挽救一个错误,我们更愿意让他`先失败`,因为监控树会保证我们的应用在错误之后,会重回到一个已知的初始状态。 135 | 136 | ## 17.4 之后 137 | 138 | 有时的确有必要时候`try/after`来保证一个资源在某些特定的动作之后被清理。例如,我们打开了一个文件并用` try/after`来保证它的关闭。 139 | 140 | ``` 141 | iex> {:ok, file} = File.open "sample", [:utf8, :write] 142 | iex> try do 143 | ...> IO.write file, "josé" 144 | ...> raise "oops, something went wrong" 145 | ...> after 146 | ...> File.close(file) 147 | ...> end 148 | ** (RuntimeError) oops, something went wrong 149 | ``` 150 | 151 | ## 17.5 变量作用域 152 | 153 | 牢记在`try/catch/rescue/after `内部定义的变量并不会泄漏到外部环境中。这是因为`try`块也会会失败,所以变量也许从一开始就是找不到的。换一句话来说,这些代码是非法的: 154 | 155 | ``` 156 | iex> try do 157 | ...> from_try = true 158 | ...> after 159 | ...> from_after = true 160 | ...> end 161 | iex> from_try 162 | ** (RuntimeError) undefined function: from_try/0 163 | iex> from_after 164 | ** (RuntimeError) undefined function: from_after/0 165 | ``` 166 | 167 | 到这里,我们结束了我们对`try`,`catch`和`rescue`的介绍。你会发现和在其他语言中相比,它们在Elixir中用的不多。当然在某些特定的情况下当一个库或一些特定的代码“不按规矩出牌”的时候,也许它们会有用。 168 | 169 | 是时候让我们谈谈一些Elixir的构建比如comprehension和sigil了。 170 | -------------------------------------------------------------------------------- /getting_started/Chapter16.md: -------------------------------------------------------------------------------- 1 | # 16 协议 2 | 3 | 协议是Elixir中实现多态的一套机制。任何数据类型,只要实现了相关的协议,都能使用这个协议。让我们看一个例子: 4 | 5 | 在Elixr中,只有`false`和`nil`被当成非真值。其他的都被认为是真值。对某些应用来说,也许实现一个`blank?`协议是非常重要的,用它来在其他数据类型上返回一个布尔值。例如,一个空列表或空二进制可以被认为是空的。 6 | 7 | 我们用下面的访法定义这个协议: 8 | 9 | ``` 10 | defprotocol Blank do 11 | @doc "Returns true if data is considered blank/empty" 12 | def blank?(data) 13 | end 14 | ``` 15 | 16 | 这个协议期待实现一个单个参数的函数的`blank?`。我们能为不同的Elixir数据类型实现这个协议: 17 | 18 | ``` 19 | # Integers are never blank 20 | defimpl Blank, for: Integer do 21 | def blank?(_), do: false 22 | end 23 | 24 | # Just empty list is blank 25 | defimpl Blank, for: List do 26 | def blank?([]), do: true 27 | def blank?(_), do: false 28 | end 29 | 30 | # Just empty map is blank 31 | defimpl Blank, for: Map do 32 | # Keep in mind we could not pattern match on %{} because 33 | # it matches on all maps. We can however check if the size 34 | # is zero (and size is a fast operation). 35 | def blank?(map), do: map_size(map) == 0 36 | end 37 | 38 | # Just the atoms false and nil are blank 39 | defimpl Blank, for: Atom do 40 | def blank?(false), do: true 41 | def blank?(nil), do: true 42 | def blank?(_), do: false 43 | end 44 | ``` 45 | 46 | 同时我们也能效法于其他的内置数据类型上。它们是: 47 | 48 | * `Atom` 49 | * `BitString` 50 | * `Float` 51 | * `Function` 52 | * `Integer` 53 | * `List` 54 | * `Map` 55 | * `PID` 56 | * `Por` 57 | * `Reference` 58 | * `Tuple` 59 | 60 | 现在用了协议和它的实现在手,我们能调用了: 61 | 62 | ``` 63 | iex> Blank.blank?(0) 64 | false 65 | iex> Blank.blank?([]) 66 | true 67 | iex> Blank.blank?([1, 2, 3]) 68 | false 69 | ``` 70 | 71 | 传递一个还没有实现协议的数据类型,会导致一个错误: 72 | 73 | ``` 74 | iex> Blank.blank?("hello") 75 | ** (Protocol.UndefinedError) protocol Blank not implemented for "hello" 76 | ``` 77 | 78 | ## 16.1 现已和structs 79 | 80 | Elixir的可扩展性的力量只有当协议和struct结合在一起的时候才显露出来。 81 | 82 | 在之前的一章,我们已经学习到了虽然struct就是表单,但它们并没有和表单共享协议的实现。然我们同在前面一章一样,定义一个`User`struct: 83 | 84 | ``` 85 | iex> defmodule User do 86 | ...> defstruct name: "jose", age: 27 87 | ...> end 88 | {:module, User, 89 | <<70, 79, 82, ...>>, {:__struct__, 0}} 90 | ``` 91 | 92 | 然后 93 | 94 | 95 | ``` 96 | iex> Blank.blank?(%{}) 97 | true 98 | iex> Blank.blank?(%User{}) 99 | ** (Protocol.UndefinedError) protocol Blank not implemented for %User{age: 27, name: "jose"} 100 | ``` 101 | 102 | 由于没有能和表单分享协议实现,struct需要它们自己的实现: 103 | 104 | 105 | ``` 106 | defimpl Blank, for: User do 107 | def blank?(_), do: false 108 | end 109 | ``` 110 | 111 | 如果需要,你可以使用你自己的对用户是否为空的定义。不仅如此,你能用struct来编写更健壮的数据类型,比如请求,和为这个数据实现所有的相关协议,例如`Enumerable`和甚至`Blank`。 112 | 113 | 在许多的实际应用中,开发者也许希望为struct提供一个默认的实现,因为为每一个struct都实现这个协议会很无聊。这时就是falling back to any显示威力的时候: 114 | 115 | # 16.2 Falling back to Any 116 | 117 | 如果我们能为所有的类型提供一个默认的实现,将是非常方便的。这可以通过在协议定义中讲设置`@fallback_to_any`为`true`来实现: 118 | 119 | ``` 120 | defprotocol Blank do 121 | @fallback_to_any true 122 | def blank?(data) 123 | end 124 | ``` 125 | 126 | 现在它可以这样被实现: 127 | 128 | ``` 129 | defimpl Blank, for: Any do 130 | def blank?(_), do: false 131 | end 132 | ``` 133 | 134 | 现在所有的那些我们还没有实现`Blank`协议的的数据类型(包括struct),都会被视为不空。 135 | 136 | # 16.3 内建协议 137 | 138 | Elixir包含了一个内建的协议。在之前的几章中,我们已经讨论了`Enum`模块就提供了许多的能通用于任何实现了`Enumerable`协议的数据类型: 139 | 140 | ``` 141 | iex> Enum.map [1, 2, 3], fn(x) -> x * 2 end 142 | [2,4,6] 143 | iex> Enum.reduce 1..3, 0, fn(x, acc) -> x + acc end 144 | 6 145 | ``` 146 | 147 | 另一个有用的例子是`String.Chars`协议,它指定了如何将一个包含字符串的数据结构转换成字符串。它是通过函数`to_string`来曝光的: 148 | 149 | ``` 150 | iex> to_string :hello 151 | "hello" 152 | ``` 153 | 154 | 注意Elixir中的字符串解析就调用了`to_string`函数: 155 | 156 | ``` 157 | iex> "age: #{25}" 158 | "age: 25" 159 | ``` 160 | 161 | 上的片段能运行因为数字实现了协议`String.Chars`。如果传递一个元组,会导致一个错误: 162 | 163 | ``` 164 | iex> tuple = {1, 2, 3} 165 | {1, 2, 3} 166 | iex> "tuple: #{tuple}" 167 | ** (Protocol.UndefinedError) protocol String.Chars not implemented for {1, 2, 3} 168 | ``` 169 | 170 | 当有需要去“打印”更复杂的数据结构的时候,简单调用`inspect`函数就行,它是基于协议`Inspect`: 171 | 172 | ``` 173 | iex> "tuple: #{inspect tuple}" 174 | "tuple: {1, 2, 3}" 175 | ``` 176 | 177 | `Inspect`协议用于把任何数据结构转换成可都的文字呈现。这也是类似IEx的工具打印的结果: 178 | 179 | ``` 180 | iex> {1, 2, 3} 181 | {1,2,3} 182 | iex> %User{} 183 | %User{name: "jose", age: 27} 184 | ``` 185 | 186 | 谨记,作为一个约定,当打印出的值以`#`开头,它是在用Elixir中非法的语法来呈现一个数据结构。这说明,inspect协议是不可逆的,因为在这个过程中有些信息丢失了。 187 | 188 | ``` 189 | iex> {1, 2, 3} 190 | {1,2,3} 191 | iex> %User{} 192 | %User{name: "jose", age: 27} 193 | ``` 194 | 195 | 除此之外,Elixir中还有一些其他的协议, 但这一章涵盖了最常见的几个。在下一章我们讲学习一点Elixir的异常和错误处理。 196 | -------------------------------------------------------------------------------- /getting_started/Chapter7.md: -------------------------------------------------------------------------------- 1 | # 7 关键字,表单和字典 2 | 3 | 迄今我们还没有讨论过相关性的数据结构,即那些把一个值(或多个值)和一个键(key)相关起来的数据结构。不同的语言对这些数据结构有不同的称呼,字典,哈希,相关性数组,表单等等。 4 | 5 | 在Elixir中,我们有两种主要的相关性数据结构:关键字列表和表单。是时候揭开它们的真面目了。 6 | 7 | ## 7.1 关键字列表 8 | 9 | 在许多的计算机语言中,经常见到用一个2元的元组来表示某种相关性的数据结构。在Elixir里,当我们有一个元组的列表并且每个元组的第一个元素是一个原子的时候,我们称为关键字列表: 10 | 11 | ``` 12 | iex> list = [{:a, 1}, {:b, 2}] 13 | [a: 1, b: 2] 14 | iex> list == [a: 1, b: 2] 15 | true 16 | iex> list[:a] 17 | 1 18 | ``` 19 | 20 | 正如你在上面列子里看到的,Elixir支持一种特殊的语法来定义这样一种列表,不过在底层它还是映射到一个元组列表的。因为它们本身就是列表,所以所有能用在列表上的函数,也能应用在它们身上,包括它们的性能特性也和列表相近。 21 | 22 | 例如,我们能用`++`把一个新的值加入到一个关键字列表中: 23 | 24 | ``` 25 | iex> list ++ [c: 3] 26 | [a: 1, b: 2, c: 3] 27 | iex> [a: 0] ++ list 28 | [a: 0, a: 1, b: 2] 29 | ``` 30 | 31 | 注意排在前面的元素在查找的时候,会被有限选取: 32 | 33 | ``` 34 | iex> new_list = [a: 0] ++ list 35 | [a: 0, a: 1, b: 2] 36 | iex> new_list[:a] 37 | 0 38 | ``` 39 | 40 | 关键字列表是非常重要的,因为它们有两个特性: 41 | 42 | * 键的顺序有开发者控制 43 | * 允许给一个键复制多次 44 | 45 | 例如,[Ecto](https://github.com/elixir-lang/ecto)利用了上面的两个特性来提供了一种优雅的DSL来编写数据库queries: 46 | 47 | ``` 48 | query = from w in Weather, 49 | where: w.prcp > 0, 50 | where: w.temp < 20, 51 | select: w 52 | ``` 53 | 54 | 这些特性促使关键字列表称为了Elixir中给函数传递参数的默认机制。在第五章,当我们讨论宏`if/2`,我们提到如下的语法是合法的: 55 | 56 | ``` 57 | iex> if false, do: :this, else: :that 58 | :that 59 | ``` 60 | 61 | `do:`和`else:`启示就是关键字列表!实际上,这个和上面的调用是等价的: 62 | 63 | ``` 64 | iex> if(false, [do: :this, else: :that]) 65 | :that 66 | ``` 67 | 68 | 总体来讲,当函数的最后一个参数是一个关键字列表的时候,方括号就不是必须的。 69 | 70 | 为了能处理关键字列表,Elixir提供了[Keyword模块](http://elixir-lang.org/docs/stable/Keyword.html)。记住虽然关键字列表本质上还是列表,它们的性能如果列表也是线性的。列表越长,在计算长度之类的操作时, 就需要更多的时间来找到某个键。因此,关键字列表在Elixir中主要被用于可选项。如果你需要存储很多元素或确定哪一个键的值是最大的,有应该用表单(map)替代。 71 | 72 | 注意我们同样能对关键字列表进行模式匹配: 73 | 74 | ``` 75 | iex> [a: a] = [a: 1] 76 | [a: 1] 77 | iex> a 78 | 1 79 | iex> [a: a] = [a: 1, b: 2] 80 | ** (MatchError) no match of right hand side value: [a: 1, b: 2] 81 | iex> [b: b, a: a] = [a: 1, b: 2] 82 | ** (MatchError) no match of right hand side value: [a: 1, b: 2] 83 | ``` 84 | 85 | 不过在实际中这很少用到,因为这需要列表的大小和元素的顺位都同时匹配。 86 | 87 | ## 7.2 表单(Maps) 88 | 89 | 在Elixir中,无论何时你需要存储一个键-值对,表单都是最佳的数据结构选择。创建一个表单需要用`%{}`语法: 90 | 91 | ``` 92 | iex> map = %{:a => 1, 2 => :b} 93 | %{2 => :b, :a => 1} 94 | iex> map[:a] 95 | 1 96 | iex> map[2] 97 | :b 98 | ``` 99 | 100 | 和关键字列表比价,我们可以清楚地看到不同: 101 | * 表单允许任何类型的键 102 | * 表单的键没有特定的顺序 103 | 104 | 当你创建表单的时候,如果有重复的键,只有最后一个才会被保留: 105 | 106 | ``` 107 | iex> %{1 => 1, 1 => 2} 108 | %{1 => 2} 109 | ``` 110 | 111 | 当一个表单中的所有键都是原子的时候,你就可以用关键字语法了: 112 | 113 | ``` 114 | iex> map = %{a: 1, b: 2} 115 | %{a: 1, b: 2} 116 | ``` 117 | 118 | 和关键字列表不同,表单在模式匹配中非常有用: 119 | 120 | ``` 121 | iex> %{} = %{:a => 1, 2 => :b} 122 | %{:a => 1, 2 => :b} 123 | iex> %{:a => a} = %{:a => 1, 2 => :b} 124 | %{:a => 1, 2 => :b} 125 | iex> a 126 | 1 127 | iex> %{:c => c} = %{:a => 1, 2 => :b} 128 | ** (MatchError) no match of right hand side value: %{2 => :b, :a => 1} 129 | ``` 130 | 131 | 正如上面的例子显示的,只要表单里有那个键,模式就能一直匹配。也就是说,一个空表单匹配所有的其他表单。 132 | 133 | 表单的一个有意思的特点是它提供了一个特有的语法来更新和访问原子类的键: 134 | 135 | 136 | ``` 137 | iex> map = %{:a => 1, 2 => :b} 138 | %{:a => 1, 2 => :b} 139 | iex> map.a 140 | 1 141 | iex> %{map | :a => 2} 142 | %{:a => 2, 2 => :b} 143 | iex> %{map | :c => 3} 144 | ** (ArgumentError) argument error 145 | ``` 146 | 147 | 在上面的例子中,更新和访问都需要特定的键的存在。例如,最后一行之所以失败是因为在表单中没有键`:c`。这一点对当你只期望表单中只包含特定的几个键时将会非常有用。 148 | 149 | 在未来的篇章中,我们将会学到结构(structs),它为Elixir的多态性提供了一个编译时的保证和基础。结构就是构件在表单之上的,上面例子中显示的表单的更新的确定性会证明是非常有用的。 150 | 151 | 对表单的处理是用过[Map模块](http://elixir-lang.org/docs/stable/Map.html)中的函数完成的,它提供了一个同关键字列表非常相似的API。这是因为表单和关键字列表都实现和字典(dict)行为。 152 | 153 | > 表单是新近才通过[EEP 443](http://elixir-lang.org/docs/stable/Map.html)被引入到Erlang虚拟机里的。Erlang 17提供了一个EEP的部分实现,它只实现了所谓的“小表单”。这意味着现在的表单只有当存储的元素数量不太大(几十个)的时候性能才有保证。为了弥补这一点,Elixir提供了[HashDict模块](http://elixir-lang.org/docs/stable/HashDict.html),这个模块用一个哈希算法提供了一个支持存储几十万计键而又能保证良好性能的字典。 154 | 155 | ## 7.3 字典(Dicts) 156 | 157 | 在Elixir中,关键字列表和表单都是字典。也就是说,一个字典就如同一个界面(interface)(在Elixir中称为行为,behaviours),关键字列表和表单都实现了这个界面。 158 | 159 | 这个界面是定义在[Dict模块](http://elixir-lang.org/docs/stable/Dict.html)之中,而且也它也提供了一套需要具体实现的API: 160 | 161 | ``` 162 | iex> keyword = [] 163 | [] 164 | iex> map = %{} 165 | %{} 166 | iex> Dict.put(keyword, :a, 1) 167 | [a: 1] 168 | iex> Dict.put(map, :a, 1) 169 | %{a: 1} 170 | ``` 171 | 字典模块允许开发者实现它们自己的字典,拥有自己的特性,并且能在现有的Elixir代码中使用。字典模块也提供了一些在所有的字典实现中都同用的函数。比如,函数`Dict.equal?/2`就能比较两个不同类型的字典。 172 | 173 | 看到这里,你可能有些迷糊了,到底该如何选择使用`Keyword`,`Map`还是`Dict`?答案是依情况而定。 174 | 175 | 如果你的代码期望把关键字当成函数参数,那就直接用`Keyword`模块。如果你想处理表单,就用`Map`模块。然而,如果你的API期望能够使用任何类型的字典,那就只能用`Dict`模块了(别忘了写测试,并且确保不同类型的字典实现都能通过测试)。 176 | 177 | 以上就是我们对Elixir中相关性数据结构的一些结论。你将会发现有了关键字列表和表单,面对任何有关需要相关性数据结构的问题时候,你都能得心应手的工具。 178 | -------------------------------------------------------------------------------- /getting_started/Chapter12.md: -------------------------------------------------------------------------------- 1 | # 12 IO 2 | 3 | 这一章将简单介绍一下Elixir的输入输出机制,和相关的模块,比如`[IO](http://elixir-lang.org/docs/stable/IO.html)`,`[File](http://elixir-lang.org/docs/stable/File.html)`和`[Path](http://elixir-lang.org/docs/stable/Path.html)`。 4 | 5 | 在一开始的时候,我们在写这个系列的教程的时候,有一个很早的比较粗躁的版本。但我们发现IO系统其实是提供了一个很好的机会来管窥Elixir和Erlang虚拟机中的一些哲学和有意思的地方。 6 | 7 | ## 12.1 IO模块 8 | 9 | Elixir中的IO模块是它的主要机制用来读写标准输入(`:stdio`),标准错误(`:stderr`),文件和其他IO设备。它的用法也是非常直观的: 10 | 11 | ``` 12 | iex> IO.puts "hello world" 13 | "hello world" 14 | :ok 15 | iex> IO.gets "yes or no? " 16 | yes or no? yes 17 | "yes\n" 18 | ``` 19 | 20 | 在默认的情况下,IO模块中的函数使用标准输入输出。我们可以通过传递参数`:stderr`来让它写进标准错误设备: 21 | 22 | ``` 23 | iex> IO.puts :stderr, "hello world" 24 | "hello world" 25 | :ok 26 | ``` 27 | # 12.2 文件模块 28 | 29 | [File](http://elixir-lang.org/docs/stable/File.html)模块包含了一些可以把文件打开当成IO设备的函数。默认情况下,文件都是用二进制模式打开,这需要开发者指明使用`IO`模块中的函数`IO.binread/2`和`IO.binwrite/2`: 30 | 31 | ``` 32 | iex> {:ok, file} = File.open "hello", [:write] 33 | {:ok, #PID<0.47.0>} 34 | iex> IO.binwrite file, "world" 35 | :ok 36 | iex> File.close file 37 | :ok 38 | iex> File.read "hello" 39 | {:ok, "world"} 40 | ``` 41 | 42 | 文件也能用`:utf8`编码模式打开,这样就可以使用`IO`模块中的其他函数了。 43 | 44 | 除了那些能打开,读写文件的函数之外,`File`模块还有很多在文件系统领域工作的函数。这些函数的名字对应于UNIX命令。例如,`File.rm/1`用于删除文件,`File.mkdir/1`用来创建文件夹, `File.mkdir/1`用来创建包含父目录的文件夹,甚至有`File.cp_r/2`和`File.rm_rf/2`用来复制和移除文件和文件夹。 45 | 46 | 你也会注意到,`File`模块中的函数可以分为两种模式,一种带有名字里带有`!`(bang),另一些没有。例如,在上面读取文件“hello”的时候,我们已经用到了一些,现在让我们在试试其他的例子: 47 | 48 | ``` 49 | iex> File.read "hello" 50 | {:ok, "world"} 51 | iex> File.read! "hello" 52 | "world" 53 | iex> File.read "unknown" 54 | {:error, :enoent} 55 | iex> File.read! "unknown" 56 | ** (File.Error) could not read file unknown: no such file or directory 57 | ``` 58 | 59 | 注意:如果文件不存在,那个带`!`的版本会抛出一个错误。也就是说,当你想自己通过模式匹配来处理不同的情况是,就选择不带`!` 60 | 的,如果你确信文件就在那儿,这个时候用带`!`的版本才有意义。简单来说,不要写成这样: 61 | 62 | ``` 63 | {:ok, body} = File.read(file) 64 | ``` 65 | 66 | 要么这样: 67 | 68 | ``` 69 | case File.read(file) do 70 | {:ok, body} -> # handle ok 71 | {:error, r} -> # handle error 72 | end 73 | ``` 74 | 或 75 | 76 | ``` 77 | File.read!(file) 78 | ``` 79 | 80 | ## 12.3 Path模块 81 | 82 | 文件模块中的大部分函数,需要的参数都是路径。最常见的情况下,这些路径都是二进制的,它们可以被通过[`Path`模块](http://elixir-lang.org/docs/stable/Path.html)来操作: 83 | 84 | ``` 85 | iex> Path.join("foo", "bar") 86 | "foo/bar" 87 | iex> Path.expand("~/hello") 88 | "/Users/jose/hello" 89 | ``` 90 | 91 | 到这里我们已经完成了IO相关和同文件系统互动的主要模块。下面我们将讨论和IO相关的趣闻和高级主题。这部分并不是编写Elixir程序必须的知识,要跳过请随意,但它们揭示了在Erlang虚拟机中IO系统是如何实现的一些趣闻。 92 | 93 | ## 12.4 进程和组领头人 94 | 95 | 你也许注意到了`File.open/2 `返回了一个包含PID的元组: 96 | 97 | ``` 98 | iex> {:ok, file} = File.open "hello", [:write] 99 | {:ok, #PID<0.47.0>} 100 | ``` 101 | 102 | 那是因为IO模块实际上是通过进程工作的。当你说`IO.write(pid, binary)`,IO模块会向一个进程发送消息,告诉它该干什么。让我们看一看如果我们使用自己的模块会发生什么: 103 | 104 | ``` 105 | iex> pid = spawn fn -> 106 | ...> receive do: (msg -> IO.inspect msg) 107 | ...> end 108 | #PID<0.57.0> 109 | iex> IO.write(pid, "hello") 110 | {:io_request, #PID<0.41.0>, #PID<0.57.0>, {:put_chars, :unicode, "hello"}} 111 | ** (ErlangError) erlang error: :terminated 112 | ``` 113 | 114 | 在`IO.write/2`之后,我们能看到打印出来IO模块发送的请求,它失败了是因为,IO模块期待得到一些回复,而我们并没有这么做。 115 | 116 | [StringIO](http://elixir-lang.org/docs/stable/elixir/StringIO.html)模块提供了在字符串之上的IO设备消息实现: 117 | 118 | ``` 119 | iex> {:ok, pid} = StringIO.start("hello") 120 | {:ok, #PID<0.43.0>} 121 | iex> IO.read(pid, 2) 122 | "he" 123 | ``` 124 | 125 | 通过用进程将IO设备模型化,Erlang虚拟机允许同一网络中的不同的节点之间交互文件进程,读写文件。在所有的IO设备中,有一个对所有每个进程都特殊的,称为组领头人: 126 | 127 | 当你写入`:stdio`,你实际上向组领头人发送了一个消息,通过它来写入`:stdio`: 128 | 129 | 130 | ``` 131 | iex> IO.puts :stdio, "hello" 132 | hello 133 | :ok 134 | iex> IO.puts Process.group_leader, "hello" 135 | hello 136 | :ok 137 | ``` 138 | 139 | 每个进程的组领头都是可以配置的,能在不同的场景中使用。例如,当在远程节点执行代码时,它保证在远程节点打印的消息会返还到“执行者”并打印。 140 | 141 | ## 12.5 `iodata` 和`char_data` 142 | 143 | 在上面的所有例子中,当我们写文件的时候,我们用到了二进制/字符串。在“二进制,字符串和字符列表”那一章,我们提到过字符串实际上就是字节,而字符列表就是codepoint的列表。 144 | 145 | `IO`和`File`模块中的函数也能接受列表作为参数。不仅如此,它们还允许接受列表中混合了列表,整数和二进制: 146 | 147 | 148 | ``` 149 | iex> IO.puts 'hello world' 150 | hello world 151 | :ok 152 | iex> IO.puts ['hello', ?\s, "world"] 153 | hello world 154 | :ok 155 | ``` 156 | 157 | 然而,这里需要注意一些事情。一个字符列表当它被写入磁盘的时候需要被编码成字节,而这依赖于IO设备的编码。如果文件打开时没有编码,文件就处在原始模式,只有`IO`模式中一`bin`开头的模式才能对付。这些函数需要一个`iodata`作为第一个参数。例如,它期待给予的参数是一个字节和二进制的列表。如果你提供了一个codepoint的列表,而且这些codepoint都大于255,这个操作将会失败,因为我们不知道如何编码。 158 | 159 | 在另一方面,`:stdio`和用`:utf8`编码打开的文件可以用IO模块中的其他函数处理,它们需要一个`char_data`的参数,例如,它们期待的参数是一个字符列表或者字符串。 160 | 161 | 虽然有这样的不同,只有当你有意传递一个列表给这些函数的时候,才需要担心。二进制的底层早就是字节了,所以它们一直就是原始模式。 162 | 163 | 当处理io数据和chat数据的时候,Elixir提供了`iodata_to_binary/1`能把任何的`iodata`转换成二进制。函数`String.from_char_data!/1`和`List.from_char_data!/1 `能被用于把chat data转成字符串或字符列表。除非你需要和一些设备打交道,不然你不会经常用到这些函数。 164 | 165 | 到这里我们就结束了IO设备和相关的函数的旅程。我们已经学到了四个有关的Elixir模块,[`IO`](http://elixir-lang.org/docs/stable/IO.html),[`File`](http://elixir-lang.org/docs/stable/File.html),[`Path`](http://elixir-lang.org/docs/stable/Path.html)和[`StringIO`](http://elixir-lang.org/docs/stable/StringIO.html),包括在底层虚拟机是如何用进程来实现IO机制,和如何用(字符和io)列表来做IO操作。 166 | -------------------------------------------------------------------------------- /getting_started/Chapter8.md: -------------------------------------------------------------------------------- 1 | # 模块 2 | 3 | 在Elixir中,相关的函数会被组合到一起,称为模块。在之前的章节中,我们已经用到了很多模块,例如[字符串模块](http://elixir-lang.org/docs/stable/String.html)。 4 | 5 | ``` 6 | iex> String.length "hello" 7 | 5 8 | ``` 9 | 10 | 要在Elixir中创建我们自己的模块,我需要使用宏`defmodule`。我们使用另一个宏`def`在模块中定义函数: 11 | 12 | ``` 13 | iex> defmodule Math do 14 | ...> def sum(a, b) do 15 | ...> a + b 16 | ...> end 17 | ...> end 18 | 19 | iex> Math.sum(1, 2) 20 | 3 21 | ``` 22 | 23 | 在接下去的部分中,我们的例子将会变得更加复杂,并且可能不太容易手动输入进iex里。不过乘此机会,我们正好可以学习一下如何编译Elixir代码和如执行Elixir脚本。 24 | 25 | ## 8.1 编译 26 | 27 | 在大部分的时候最好将模块写入文件,以便于编译和重用。这里我们假定我们已经有了一个文件`math.ex`,包含如下的代码: 28 | 29 | ```elixir 30 | defmodule Math do 31 | def sum(a, b) do 32 | a + b 33 | end 34 | end 35 | ``` 36 | 37 | 我们可以用`elixirc`来编译这个文件: 38 | 39 | ```bash 40 | elixirc math.ex 41 | ``` 42 | 43 | 这会产生一个对应模块的字节码文件的`Elixir.Math.beam`。如果在这个文件所在的目录,我们重新开始`iex`, 我们先前定义的模块就可以使用了。 44 | 45 | ``` 46 | iex> Math.sum(1, 2) 47 | 3 48 | ``` 49 | 50 | Elixir的项目通常会包含至少以下三个目录: 51 | * ebin - 包含编译后的字节码 52 | * lib - 包含Elixir源代码 (通常是`.ex`文件) 53 | * test - 包含测试(通常是 `.exs`文件) 54 | 55 | 在实际的项目中,用到的编译工具是`mix`,它负责设置正确的路径并编译。为了方便学习,Elixir也提供了一个更加灵活的脚本模式,无需编译可以之间运行。 56 | 57 | ## 8.2 脚本模式 58 | 59 | 除了常见的Elixir源码文件扩展名`.ex`,Elixir还支持`.exs`文件作为脚本。Elixir对两种文件是一视同仁的,唯一的区别在于`.ex`文件必须编译的,而`.exs`无需编译就可以之间运行。举例来说,我们可以创建一个叫`math.exs`的文件: 60 | 61 | ``` 62 | defmodule Math do 63 | def sum(a, b) do 64 | a + b 65 | end 66 | end 67 | 68 | IO.puts Math.sum(1, 2) 69 | ``` 70 | 71 | 然后执行文件: 72 | 73 | ``` 74 | elixir math.exs 75 | ``` 76 | 77 | 这个文件将会被在内存中编译并执行,然后打印出结果“3”。它不产生字节码。对于下面的例子,我们建议你把你的代码写入脚本中,然后按照上面的方式执行。 78 | 79 | ## 8.3 有名函数 80 | 81 | 在模块内部,我们可以用`def/2`定义函数,用`defp/2`定义私有函数。用`def/2`定义的函数能够外部的模块调用而私有函数只能被从模块内部使用。 82 | 83 | ``` 84 | defmodule Math do 85 | def sum(a, b) do 86 | do_sum(a, b) 87 | end 88 | 89 | defp do_sum(a, b) do 90 | a + b 91 | end 92 | end 93 | 94 | Math.sum(1, 2) #=> 3 95 | Math.do_sum(1, 2) #=> ** (UndefinedFunctionError) 96 | ``` 97 | 98 | 函数申明同时也支持守护和多子句。如果一个函数有多个子句,Elixir会尝试每一个子句直到发现匹配的那个。下面的例子实现了一个函数去检查输入的数字是否是零: 99 | 100 | ``` 101 | defmodule Math do 102 | def zero?(0) do 103 | true 104 | end 105 | 106 | def zero?(x) when is_number(x) do 107 | false 108 | end 109 | end 110 | 111 | Math.zero?(0) #=> true 112 | Math.zero?(1) #=> false 113 | 114 | Math.zero?([1,2,3]) 115 | #=> ** (FunctionClauseError) 116 | ``` 117 | 118 | 如果一个参数无法匹配任何一个子句,会导致一个错误。 119 | 120 | ## 8.4 函数捕捉 121 | 122 | 在这篇教程中,我们一直都用`函数名/参数量`的方式指向函数。用这种方式也可以用来获取模块中的有名函数。让我们重新打开`iex`,并运行之前定义的`math.exs`脚本: 123 | 124 | ``` 125 | $ iex math.exs 126 | ``` 127 | 128 | ``` 129 | iex> Math.zero?(0) 130 | true 131 | iex> fun = &Math.zero?/1 132 | &Math.zero?/1 133 | iex> is_function fun 134 | true 135 | iex> fun.(0) 136 | true 137 | ``` 138 | 139 | 本地函数或者已经引入的其他模块的函数,比如`is_function/1`, 没有模块也可以被捕捉。 140 | 141 | ``` 142 | iex> &is_function/1 143 | &:erlang.is_function/1 144 | iex> (&is_function/1).(fun) 145 | true 146 | ``` 147 | 148 | 注意捕捉语法也是一种定义函数的快捷方式: 149 | 150 | ``` 151 | iex> fun = &(&1 + 1) 152 | #Function<6.71889879/1 in :erl_eval.expr/5> 153 | iex> fun.(1) 154 | 2 155 | ``` 156 | 157 | 上面例子中`&1`是传给函数的第一个参数。`&(&1 + 1)`等价于`fn x -> x + 1 end`。上面的语法适合于定义短小的函数。更多的关于函数捕捉操作符`&`,请参考[Kernel.SpecialForms文档](http://elixir-lang.org/docs/stable/Kernel.SpecialForms.html)。 158 | 159 | ## 8.5 默认参数 160 | 161 | Elixir中的有名函数也支持默认参数: 162 | 163 | ``` 164 | defmodule Concat do 165 | def join(a, b, sep \\ " ") do 166 | a <> sep <> b 167 | end 168 | end 169 | 170 | IO.puts Concat.join("Hello", "world") #=> Hello world 171 | IO.puts Concat.join("Hello", "world", "_") #=> Hello_world 172 | ``` 173 | 174 | 默认参数值可以是任何一个表达式,但不会在函数定义时执行。它只是被存储在哪里。每次函数被调用的时候,所有的默认参数值都会被使用,代表参数值的表达式就会被执行: 175 | 176 | ``` 177 | defmodule DefaultTest do 178 | def dowork(x \\ IO.puts "hello") do 179 | x 180 | end 181 | end 182 | ``` 183 | 184 | ``` 185 | iex> DefaultTest.dowork 123 186 | 123 187 | iex> DefaultTest.dowork 188 | hello 189 | :ok 190 | ``` 191 | 192 | 在一个一个带有默认参数的函数有多个子句,我们建议创建一个函数头(无需实际的函数体),专用于申明默认参数: 193 | 194 | ``` 195 | defmodule Concat do 196 | def join(a, b \\ nil, sep \\ " ") 197 | 198 | def join(a, b, _sep) when nil?(b) do 199 | a 200 | end 201 | 202 | def join(a, b, sep) do 203 | a <> sep <> b 204 | end 205 | end 206 | 207 | IO.puts Concat.join("Hello", "world") #=> Hello world 208 | IO.puts Concat.join("Hello", "world", "_") #=> Hello_world 209 | IO.puts Concat.join("Hello") #=> Hello 210 | ``` 211 | 212 | 在使用默认参数值的时候,注意避免覆盖函数定义。考虑下面的例子: 213 | 214 | ``` 215 | defmodule Concat do 216 | def join(a, b) do 217 | IO.puts "***First join" 218 | a <> b 219 | end 220 | 221 | def join(a, b, sep \\ " ") do 222 | IO.puts "***Second join" 223 | a <> sep <> b 224 | end 225 | end 226 | ``` 227 | 228 | 如果我们把上面的代码保存到一个文件“concat.ex”,并编译。Elixir会发出下面的警告: 229 | 230 | ``` 231 | concat.exs:7: this clause cannot match because a previous clause at line 2 always matches 232 | ``` 233 | 234 | 编译器在告诉我们当用两个参数代用函数`join`只会选择`join`的第一个定义,而第二个定义只有在三个参数的时候才会被选中。 235 | 236 | ``` 237 | $ iex concat.exs 238 | ``` 239 | 240 | ``` 241 | iex> Concat.join "Hello", "world" 242 | ***First join 243 | "Helloworld" 244 | ``` 245 | 246 | ``` 247 | iex> Concat.join "Hello", "world", "_" 248 | ***Second join 249 | "Hello_world" 250 | ``` 251 | 252 | 到这里我们对模块的简介就结束了。在下面的几章,我们将学习如何用函数递归,Elixir中的可以从别的模块中引入函数的语法工具,以及讨论模块的属性。 253 | -------------------------------------------------------------------------------- /getting_started/Chapter5.md: -------------------------------------------------------------------------------- 1 | # 5 `case`,`cond`和`if` 2 | 3 | 在这一章中我们将学习`case`,`cond`和`if`的流程控制结构。 4 | 5 | ## 5.1 `case` 6 | 7 | `case`允许我们对很多模式的值进行比较,知道找到匹配的: 8 | 9 | ``` 10 | iex> case {1, 2, 3} do 11 | ...> {4, 5, 6} -> 12 | ...> "This clause won't match" 13 | ...> {1, x, 3} -> 14 | ...> "This clause will match and bind x to 2 in this clause" 15 | ...> _ -> 16 | ...> "This clause would match any value" 17 | ...> end 18 | ``` 19 | 20 | 如果你想对一个现存的变量进行模式匹配,你需要`^`操作符: 21 | 22 | ``` 23 | iex> x = 1 24 | 1 25 | iex> case 10 do 26 | ...> ^x -> "Won't match" 27 | ...> _ -> "Will match" 28 | ...> end 29 | ``` 30 | 31 | 字句也允许用附加调教作为守护: 32 | 33 | ``` 34 | iex> case {1, 2, 3} do 35 | ...> {1, x, 3} when x > 0 -> 36 | ...> "Will match" 37 | ...> _ -> 38 | ...> "Won't match" 39 | ...> end 40 | ``` 41 | 42 | 在上面的例子中,第一个字句只有当`x`的值为正的时候才匹配。Erlang虚拟机只允许有限的一些表达式作为守护: 43 | 44 | * 比较操作符(`==`, `!=`, `===`, `!==`, `>`, `<`, `<=`, `>=`) 45 | * 布尔值操作符(`and`, `or`) and negation operators (`not`, `!`) 46 | * 运算操作符 (`+`, `-`, `*`, `/`) 47 | * 只要左侧是一个literal的时候都能用`<>` 和 `++` 48 | * `in` 操作符 49 | * 下面的这些类型检查hanshu: 50 | 51 | * `is_atom/1` 52 | * `is_binary/1` 53 | * `is_bitstring/1` 54 | * `is_boolean/1` 55 | * `is_float/1` 56 | * `is_function/1` 57 | * `is_function/2` 58 | * `is_integer/1` 59 | * `is_list/1` 60 | * `is_map/1` 61 | * `is_number/1` 62 | * `is_pid/1` 63 | * `is_port/1` 64 | * `is_reference/1` 65 | * `is_tuple/1` 66 | * 外加这些函数: 67 | 68 | * `abs(number)` 69 | * `bit_size(bitstring)` 70 | * `byte_size(bitstring)` 71 | * `div(integer, integer)` 72 | * `elem(tuple, n)` 73 | * `hd(list)` 74 | * `length(list)` 75 | * `map_size(map)` 76 | * `node()` 77 | * `node(pid | ref | port)` 78 | * `rem(integer, integer)` 79 | * `round(number)` 80 | * `self()` 81 | * `size(tuple | bitstring)` 82 | * `tl(list)` 83 | * `trunc(number)` 84 | * `tuple_size(tuple)` 85 | 86 | 谨记,守护函数中的错误是不可见的,它只能让守护函数失效: 87 | 88 | ``` 89 | iex> hd(1) 90 | ** (ArgumentError) argument error 91 | :erlang.hd(1) 92 | iex> case 1 do 93 | ...> x when hd(x) -> "Won't match" 94 | ...> x -> "Got: #{x}" 95 | ...> end 96 | "Got 1" 97 | ``` 98 | 99 | 如果没有任何一个子句匹配,会导致一个错误: 100 | 101 | ``` 102 | iex> case :ok do 103 | ...> :error -> "Won't match" 104 | ...> end 105 | ** (CaseClauseError) no case clause matching: :ok 106 | ``` 107 | 108 | 注意匿名函数也能用好几个子句和守护: 109 | 110 | ``` 111 | iex> f = fn 112 | ...> x, y when x > 0 -> x + y 113 | ...> x, y -> x * y 114 | ...> end 115 | #Function<12.71889879/2 in :erl_eval.expr/5> 116 | iex> f.(1, 3) 117 | 4 118 | iex> f.(-1, 3) 119 | -3 120 | ``` 121 | 122 | 每个匿名函数的子句所接受的参数量必须相同,不然也会报错。 123 | 124 | ## 5.2 `cond` 125 | 126 | 当你需要匹配不同的值的时候`case`会非常有用。然而,在很多情况下,我们要检查不同的状况并找出第一个为真的。在这种情况下,可以考虑使用`cond`: 127 | 128 | ``` 129 | iex> cond do 130 | ...> 2 + 2 == 5 -> 131 | ...> "This will not be true" 132 | ...> 2 * 2 == 3 -> 133 | ...> "Nor this" 134 | ...> 1 + 1 == 2 -> 135 | ...> "But this will" 136 | ...> end 137 | "But this will" 138 | ``` 139 | 140 | 它等价于很多其他语言中的`else if`子句,Elixir也有`if`和`else`不过用的较少。 141 | 142 | 如果所有的条件都不为真,会导致一个错误。因此,有必要把最后一个条件设置为`true`,这样就总能匹配了: 143 | 144 | ``` 145 | iex> cond do 146 | ...> 2 + 2 == 5 -> 147 | ...> "This is never true" 148 | ...> 2 * 2 == 3 -> 149 | ...> "Nor this" 150 | ...> true -> 151 | ...> "This is always true (equivalent to else)" 152 | ...> end 153 | ``` 154 | 155 | 最后,请注意在`cond`眼里,除了`nil`和`false`之外的所有值都为真: 156 | 157 | ``` 158 | iex> cond do 159 | ...> hd([1,2,3]) -> 160 | ...> "1 is considered as true" 161 | ...> end 162 | "1 is considered as true" 163 | ``` 164 | 165 | # 5.3 `if`和`unless` 166 | 167 | 除了`case`和`cond`,当你值需要检查一个条件的时候,Elixir也提供了两个有用的宏`if/2`和`unless/2`: 168 | 169 | ``` 170 | iex> if true do 171 | ...> "This works!" 172 | ...> end 173 | "This works!" 174 | iex> unless true do 175 | ...> "This will never be seen" 176 | ...> end 177 | nil 178 | ``` 179 | 180 | 在上面的例子中,如果提供给`if/2`发挥`false`或者`nil`,`do/end`之间的代码就不执行,只返回`nil`。反之就是`unless/2`的情况。 181 | 182 | 它们也支持`else`: 183 | 184 | ``` 185 | iex> if nil do 186 | ...> "This won't be seen" 187 | ...> else 188 | ...> "This will" 189 | ...> end 190 | "This will" 191 | ``` 192 | > `if/2`和`unless/2`有趣的地方在于它们在语言中是用宏的方式实现的,和在其他语言中一样它们是语言的特殊构件。你可以在[kernel模块文档](http://elixir-lang.org/docs/stable/Kernel.html)里查阅`if/2`的文档和代码。类似`+/2`这样的操作符和`is_function/2`之类的函数都位于`Kernel`模块,当程序启动的时候它们会自动被载入。 193 | 194 | 195 | # 5.4 `do` 块 196 | 197 | 到目前为止,我们已经学习了四种控制结构:`case`,`cond`,`if`和`unless`,并且它们都被`do`/`end`块所包含。其实`if`也能写成这样: 198 | 199 | ``` 200 | iex> if true, do: 1 + 2 201 | 3 202 | ``` 203 | 204 | 在Elixir中,`do`/`end`块是一种把一堆表带式传给`do:`的方便表示。下面两个是等价的: 205 | 206 | ``` 207 | iex> if true do 208 | ...> a = 1 + 2 209 | ...> a + 10 210 | ...> end 211 | 13 212 | iex> if true, do: ( 213 | ...> a = 1 + 2 214 | ...> a + 10 215 | ...> ) 216 | 13 217 | ``` 218 | 219 | 我们说第二中语法用了**关键字列表**。`else`也可以用同样的表达: 220 | 221 | ``` 222 | iex> if false, do: :this, else: :that 223 | :that 224 | ``` 225 | 226 | 牢记一个细节非常重要,当我们用`do`/`end`块,它们总是和最远的那个函数调用绑定。例如,下面的表达式: 227 | 228 | ``` 229 | iex> is_number if true do 230 | ...> 1 + 2 231 | ...> end 232 | ``` 233 | 234 | 会被解析成: 235 | 236 | ``` 237 | iex> is_number(if true) do 238 | ...> 1 + 2 239 | ...> end 240 | ``` 241 | 242 | 这会导致Elixir试图去调用函数`if/1`, 当然会抛出一个未定义函数错误。加上圆括号可以避免这样的歧义: 243 | 244 | ``` 245 | iex> is_number(if true do 246 | ...> 1 + 2 247 | ...> end) 248 | true 249 | ``` 250 | 251 | 关键字列表在这个语言中扮演了一个重要的角色,在很多的函数和宏中都广泛地应用。我们将未来的章节中对它们进行进一步的的探索。现在是时候谈谈关于“二进制,字符串和字符列表”了。 252 | -------------------------------------------------------------------------------- /getting_started/Chapter2.md: -------------------------------------------------------------------------------- 1 | # 2 基础数据结构 2 | 3 | 在这章中我们将学习Elixir中的一些基础的数据结构:整数,浮点数,原子,列表和字符串。它们是: 4 | 5 | ``` 6 | iex> 1 # integer 7 | iex> 0x1F # integer 8 | iex> 1.0 # float 9 | iex> :atom # atom / symbol 10 | iex> "elixir" # string 11 | iex> [1, 2, 3] # list 12 | iex> {1, 2, 3} # tuple 13 | ``` 14 | 15 | ## 2.1 基础运算 16 | 17 | 打开`iex`, 输入下面的表达式: 18 | 19 | ``` 20 | iex> 1 + 2 21 | 3 22 | iex> 5 * 5 23 | 25 24 | iex> 10 / 2 25 | 5.0 26 | ``` 27 | 28 | 你可能注意到了,在Elixir中调用函数时圆括号并不是必须的。 29 | 30 | Elixir同时也支持二进制,八进制和十六进制的数字: 31 | 32 | ``` 33 | iex> 0x1F 34 | 31 35 | iex> 0o777 36 | 511 37 | iex> 0b1010 38 | 10 39 | ``` 40 | 41 | 浮点数表达式需要在一个点之后跟随至少一位数字,同时也支持`e`的乘方的形式: 42 | 43 | ``` 44 | iex> 1.0 45 | 1.0 46 | iex> 1.0e-10 47 | 1.0e-10 48 | ``` 49 | 50 | 在Elixir中,浮点数都是64位双精度。 51 | 52 | ## 2.2 布尔值 53 | 54 | Elixir支持两种布尔值,`true`和`false`。 55 | 56 | ``` 57 | iex> true 58 | true 59 | iex> true == false 60 | false 61 | ``` 62 | 63 | Elixr提供了一些谓语函数用来检查值的类型。比如,函数`is_boolean/1`能被用来检查一个值是不是布尔值: 64 | 65 | > 注意:在Elixir里,用函数名加参数量的组合来识别一个函数。因此,`is_boolean/1`代表一个函数的名字是`is_boolean`,接受的参数量是一个。而`is_boolean/2`(并非真实存在于Elixir里)是另一个函数,虽然名字和之前那个相同,但是接受不同参数的参数。 66 | 67 | ``` 68 | iex> is_boolean(true) 69 | true 70 | iex> is_boolean(1) 71 | false 72 | ``` 73 | 74 | 你也可以用`is_integer/1`来检查参数是否是整数,用`is_float/1`检查参数是否是浮点数或者用`is_number/1`检查参数是否是上面两个其中之一。 75 | 76 | > 注意:在任何时候你都可以在控制台中键入`h`来打印出帮助信息。`h`函数也能被用来访问函数的文档。例如,输入`h is_integer/1`会打印出函数`is_integer/1`的文档。它也能用在操作符和其他的构件上(试试`h ==/2`)。 77 | 78 | ## 2.3 原子 79 | 80 | 原子是一些以名字为值的恒量。在其他的一些语言中,也称为符号(Symbols)。 81 | 82 | ``` 83 | iex> :hello 84 | :hello 85 | iex> :hello == :world 86 | false 87 | ``` 88 | 事实上,布尔值`true`和`false`就是原子: 89 | 90 | ``` 91 | iex> true == :true 92 | true 93 | iex> is_atom(false) 94 | true 95 | ``` 96 | ## 2.4 字符串 97 | 98 | 在Elixir中字符串必须用双引号来表达,并且用UTF-8来编码: 99 | 100 | ``` 101 | iex> "hellö" 102 | "hellö" 103 | ``` 104 | 105 | Elixir也支持字符串嵌套: 106 | 107 | ``` 108 | iex> "hellö #{:world}" 109 | "hellö world" 110 | ``` 111 | 结束符可以直接放置在字符串中,也可以用escape sequences: 112 | 113 | ``` 114 | iex> "hello 115 | ...> world" 116 | "hello\nworld" 117 | iex> "hello\nworld" 118 | "hello\nworld" 119 | ``` 120 | 121 | 你能用`IO`模块中的函数`IO.puts/1`来打印字符串: 122 | 123 | ``` 124 | iex> IO.puts "hello\nworld" 125 | hello 126 | world 127 | :ok 128 | ``` 129 | 130 | 注意函数在打印完之后用原子作为返回值。 131 | 132 | 在Elixir中,字符串在底层是用二进制,也就是字节来实现的: 133 | 134 | ``` 135 | iex> is_binary("hellö") 136 | true 137 | ``` 138 | 139 | 我们也能得到一个字符串的字节数: 140 | 141 | ``` 142 | iex> byte_size("hellö") 143 | 6 144 | ``` 145 | 146 | 请注意在上面的例子中,虽然字符串只有5个字符,但它的字节数是6。这是因为在UTF-8中字符"ö"需要占用两个字节。要得到字符串的实际字符长度,我们可以用函数`String.length/1`: 147 | 148 | ``` 149 | iex> String.length("hellö") 150 | 5 151 | ``` 152 | 153 | Elixir标准库中的[字符串模块](http://elixir-lang.org/docs/stable/String.html)包含了一些用来对字符串进行Unicode标准操作的函数: 154 | 155 | ``` 156 | iex> String.upcase("hellö") 157 | "HELLÖ" 158 | ``` 159 | 160 | 谨记,在Elixir里_双引号字符串_和_单引号字符串_是不一样的,它们的底层实现的数据类型是不同的: 161 | 162 | ``` 163 | iex> 'hellö' == "hellö" 164 | false 165 | ``` 166 | 167 | 我们会在第六章`二进制,字符串和字符列表`中学习到更多关于Unicode支持和单双引号字符串之间的不同之处。 168 | 169 | ## 2.5 匿名函数 170 | 171 | 函数用关键值`fn`和`end`来表达: 172 | 173 | ``` 174 | iex> add = fn a, b -> a + b end 175 | #Function<12.71889879/2 in :erl_eval.expr/5> 176 | iex> is_function(add) 177 | true 178 | iex> is_function(add, 2) 179 | true 180 | iex> is_function(add, 1) 181 | false 182 | iex> add.(1, 2) 183 | 3 184 | ``` 185 | 186 | 在Elixir中函数是“第一等公民”,这意味着它们能被想整数和字符串一样当成参数传给别的函数。在上面的例子里,我们把变量指向的函数`add`传给了另一个函数`is_function/1`,并且返回了正确的结果,`true`。我们也能用`is_function/2`来检查一个函数接受的函数数量。 187 | 188 | 注意当调用一个匿名函数时,在指向这个匿名函数的变量名和圆括号之间需要有一个点号(`.`)。 189 | 190 | 匿名函数本身也是一个闭包,因此它们能够访问在被定义时的同视域的其他变量: 191 | 192 | ``` 193 | iex> add_two = fn a -> add.(a, 2) end 194 | #Function<6.71889879/1 in :erl_eval.expr/5> 195 | iex> add_two.(2) 196 | 4 197 | ``` 198 | 199 | 牢记,在一个函数内部的变量赋值, 并不影响函数外部的环境: 200 | 201 | ``` 202 | iex> x = 42 203 | 42 204 | iex> (fn -> x = 0 end).() 205 | 0 206 | iex> x 207 | 42 208 | ``` 209 | 210 | ## 2.6 (链接)列表 211 | 212 | Elixir用方括号来表示一个列表,列表内元素的类型是随意的: 213 | 214 | ``` 215 | iex> [1, 2, true, 3] 216 | [1, 2, true, 3] 217 | iex> length [1, 2, 3] 218 | 3 219 | ``` 220 | 221 | 两个列表可以用函数`++/2 `,`--/2`进行合并或相异操作: 222 | 223 | ``` 224 | iex> [1, 2, 3] ++ [4, 5, 6] 225 | [1, 2, 3, 4, 5, 6] 226 | iex> [1, true, 2, false, 3, true] -- [true, false] 227 | [1, 2, 3, true] 228 | ``` 229 | 230 | 贯穿整个教程,我们会不停地谈到列表的头(head)和尾(tail)。头是列表中的第一个元素,尾是剩下的。它们可以被分别用函数`hd/1`和`tl/1`得到。让我们创建一个列表,试着获取头和尾: 231 | 232 | ``` 233 | iex> list = [1,2,3] 234 | iex> hd(list) 235 | 1 236 | iex> tl(list) 237 | [2, 3] 238 | ``` 239 | 240 | 试图获取一个空列表的头将会导致一个错误: 241 | 242 | ``` 243 | iex> hd [] 244 | ** (ArgumentError) argument error 245 | ``` 246 | 247 | Oops! 248 | 249 | ## 2.7 元组(Tuples) 250 | 251 | Elixir用花括号来表示元组。和列表一样,元组能包含任何类型的元素: 252 | 253 | ``` 254 | iex> {:ok, "hello"} 255 | {:ok, "hello"} 256 | iex> size {:ok, "hello"} 257 | 2 258 | ``` 259 | 260 | 元组中的元素在内存中是连续的。也就是说,用索引来访问元组中的元素或者获取元组的大小这样的操作是非畅快的。(元组的索引是从0开始的): 261 | 262 | ``` 263 | iex> tuple = {:ok, "hello"} 264 | {:ok, "hello"} 265 | iex> elem(tuple, 1) 266 | "hello" 267 | iex> tuple_size(tuple) 268 | 2 269 | ``` 270 | 271 | 用函数`set_elem/3`可以把一个元素放置在一个特定的索引上: 272 | 273 | ``` 274 | iex> tuple = {:ok, "hello"} 275 | {:ok, "hello"} 276 | iex> set_elem(tuple, 1, "world") 277 | {:ok, "world"} 278 | iex> tuple 279 | {:ok, "hello"} 280 | ``` 281 | 282 | 注意`set_elem/3`返回一个新的元组。因为在Elixri中的数据类型都是不可变的,所以变量`tuple`中的那个老的元组并没有变化。不可变量带来的一个好处是,它使得Elixir的代码变得相对简单,因为你不需要担心有一些代码会破坏某处的一个数据。 283 | 284 | 同时,不可变量也对避免一些常见的问题有帮助,比如并行代码中的race conditions,当两个以上的实体在统一时间试图修改同一个数据结构。 285 | 286 | ## 2.8 列表还是元组 287 | 288 | 列表和元组的不同之处在哪里? 289 | 290 | 在内存中列表是互相链接的形式存在的。这意味着列表中的每一个元素都指向下一个元素,直到到达列表的最后。我们管每一个这样的组对**cons cell**: 291 | 292 | ``` 293 | iex> list = [1|[2|[3|[]]]] 294 | [1, 2, 3] 295 | ``` 296 | 297 | 这也意味着获取一个类别的长度是一个线性操作:我们必须贯穿真个列表来弄清楚真个列表的大小。用把新元素插入列表头部的方式,来更新列表是一个比较快的操作: 298 | 299 | ``` 300 | iex> [0] ++ list 301 | [0, 1, 2, 3] 302 | iex> list ++ [4] 303 | [1, 2, 3, 4] 304 | ``` 305 | 306 | 在上面的例子中,第一个操作是比较块的,因为我们只是加入了一个新的元组,并且让它指向旧的列表。第二个列子就比较慢了,因为我们必须重建整个列表然后才能加入新元素。 307 | 308 | 从另一方面来说,元组在内存中是一个整体。这意味着获取它的大小或通过索引来访问元素是非常快的。然而,修改或增减元组中的元素是非常昂贵的操作,因为它首先需要在内存中复制整个元组。 309 | 310 | 更具这些性能上的特点,我们可以来决定两者的不同用途。一个非常常见的例子是用函数返回一个元组来表示附加的信息。比如,`File.read/1`用来读取一个文件的内同,它返回的就是一个元组: 311 | 312 | ``` 313 | iex> File.read("path/to/existing/file") 314 | {:ok, "... contents ..."} 315 | iex> File.read("path/to/unknown/file") 316 | {:error, :enoent} 317 | ``` 318 | 319 | 在Elixir中,当我们计算一个数据结构的大小时,需要遵循一个简单的原则:当这个操作所需的时间是一个恒量(也就是说,这个数值是事先已经计算好了的)的时候,函数应该用`size`来命名,否这的话应该用`length`。 320 | 321 | 例如,迄今我们遇到过4个此类的函数:`byte_size/1`(获取字符串中的字节数),`tuple_size/1`(获取元组的大小),`length/1`(获取列表的大小),`String.length/1`(获取字符串中的字符数)。可见,当我们用`byte_size/1`去获得字符串中的字节数的时候,这个操作是相当简单的,但当我们希望用`String.length/1`得到其中的unicode字符的数量时是非常昂贵的,因为我们需要历遍整个字符串。 322 | 323 | Elixir同时也支持其他的一些数据类型,`Port`,`Reperence`和`PID`,用于进程间的通许。当讲到进程相关的章节的时候,我们会具体谈到它们。现在让我们了解一下和基础数据结构相关的一些基础操作符。 324 | -------------------------------------------------------------------------------- /mix/Chapter1.md: -------------------------------------------------------------------------------- 1 | # 简介 2 | 3 | * 4 | * 5 | * 6 | * 7 | * 8 | * 9 | * 10 | * 11 | * 12 | * 13 | * 14 | * 15 | * 16 | * 17 | * 18 | 19 | Elixir自带了几个应用使得编写和部署Elixir项目更加容易,其中Mix是关键。 20 | 21 | Mix是一个提供了用于创建,编译,测试(很快就会有发布功能)的编译工具。Mix的灵感来自于Clojure的编译工具Leiningen,并且作者本人就是它的其中一个开发者。 22 | 23 | 在这一章,我们将学习如何用mix来创建项目,安装依赖。在之后的部分,我们将雪鞋如何创建OTP应用,和定制mix的任务。 24 | 25 | # 1.1 始动 26 | 27 | 要开始你的第一个项目,你只需要输入`mix new` 并把项目的路径作为参数。现在,我们将在当前目录创建一个被称为``的项目: 28 | 29 | ``` 30 | $ mix new my_project --bare 31 | ``` 32 | 33 | Mix将创建一个名为`my_project`的目录,包含一下这些内容: 34 | 35 | ``` 36 | .gitignore 37 | README.md 38 | mix.exs 39 | lib/my_project.ex 40 | test/test_helper.exs 41 | test/my_project_test.exs 42 | ``` 43 | 44 | 让我们来简单地看一下其中的一些。 45 | 46 | > 注意:Mix是一个Elixir的可执行文件。这意味着为了能够运行`mix`,elixir的可执行文件许需要在你的`PATH`里面。如果不是,你可以直接把脚本作为参数传递给elixir来执行: 47 | 48 | >``` 49 | >$ bin/elixir bin/mix new ./my_project 50 | >``` 51 | >注意你也能通过``选项来让Elixir运行任何PATH里的脚本: 52 | >``` 53 | >$ bin/elixir -S mix new ./my_project 54 | >``` 55 | >当用了``, elixir会遍历PATH,寻找并运行那个脚本 56 | 57 | # 1.1.1 mix.exs 58 | 59 | 这是包含了你项目配置的文件。它看起来是这样的: 60 | 61 | ``` 62 | defmodule MyProject.Mixfile do 63 | use Mix.Project 64 | 65 | def project do 66 | [app: :my_project, 67 | version: "0.0.1", 68 | deps: deps] 69 | end 70 | 71 | # Configuration for the OTP application 72 | def application do 73 | [] 74 | end 75 | 76 | # Returns the list of dependencies in the format: 77 | # {:foobar, git: "https://github.com/elixir-lang/foobar.git", tag: "0.1"} 78 | # 79 | # To specify particular versions, regardless of the tag, do: 80 | # {:barbat, "~> 0.1", github: "elixir-lang/barbat"} 81 | defp deps do 82 | [] 83 | end 84 | end 85 | ``` 86 | 87 | 我们的``定义了两个函数:``, 用来返回项目的配置比如项目名称和版本,和``,用来产生一个被Erlang运行时管理的Erlang应用。在这一章,我们将谈一谈函数``。我们将在下一章详谈``函数。 88 | 89 | # 1.1.2 90 | 91 | 这个文件包含了一个简单的模块,它定义了我们代码的基本机构: 92 | 93 | ``` 94 | defmodule MyProject do 95 | end 96 | ``` 97 | 98 | # 1.1.3 99 | 100 | 这个文件包含了项目的一个测例: 101 | 102 | ``` 103 | defmodule MyProjectTest do 104 | use ExUnit.Case 105 | 106 | test "the truth" do 107 | assert true 108 | end 109 | end 110 | ``` 111 | 112 | 有几点请注意: 113 | 114 | 1) 注意这个文件是一个Elixir的脚本文件(``)。作为一个约定,我们不需要在运行之前编译测试。 115 | 116 | 2) 我们定义了一个测试模块``,用``来注入默认的行为,并定义了一个简单测试。你可以在ExUnit那一章学习到有关测试框架的更多内容。 117 | 118 | # 1.1.4 119 | 120 | 我们将查看的最后一个文件是``,它的任务是启动测试框架: 121 | 122 | ``` 123 | ExUnit.start 124 | ``` 125 | # 1.2 探索 126 | 127 | 现在我们已经创建了新项目,接下去做什么?要了解还有其他什么命令可以使用的话,运行`help`任务: 128 | 129 | ``` 130 | $ mix help 131 | ``` 132 | 133 | 它将会打印出所有可得任务,你能得到用`mix help TASK`得到更多的信息。 134 | 135 | 运行其中一些命令试试,比如`mix compile`和`mix test`,在你的项目里运行看看会发生什么。 136 | 137 | # 1.3 编译 138 | 139 | Mix可以我们编译项目。默认的设置是用``放源代码,``放编译后的beam文件。你无需提供任何的编译相关的设置,但如果你决定这么做,有一些选项可以用。例如,如果你打算把你的编译后的beam文件放在`ebin`之外的文件夹里,只需要在``里设置``: 140 | 141 | ``` 142 | def project do 143 | [compile_path: "ebin"] 144 | end 145 | ``` 146 | 147 | 总的来说,Mix会尽力表现的聪明一些,只在必须的时候编译。 148 | 149 | 注意在你第一次编译之后,Mix会在你的`ebin`文件夹里产生了一个``文件。这个文件里定义的Erlang应用是用到了你的项目中的``函数里的内容。 150 | 151 | 这个``文件存储在和应用有关的信息,它的依赖,它所依赖的模块㩐等。每次你用mix运行命令的时候,这个应用会自动被启动,我们将在下一章学习如何配置它。 152 | 153 | 1.4 依赖 154 | 155 | Mix也能用来管理依赖。依赖应被列在项目配置中,例如: 156 | 157 | ``` 158 | def project do 159 | [app: :my_project, 160 | version: "0.0.1", 161 | deps: deps] 162 | end 163 | 164 | defp deps do 165 | [{:some_project, ">= 0.3.0"}, 166 | {:another_project, git: "https://example.com/another/repo.git", tag: "v1.0.2"}] 167 | end 168 | ``` 169 | 170 | **注意:** 虽然并非必须,常见的做法是把依赖分散到它们自己的函数里。 171 | 172 | 某个依赖有一个原子来表示,跟着是一个需求和一些选项。在默认情况下,Mix使用``来获取依赖,但它也能从git库或直接从文件系统来获取。 173 | 174 | 当我们使用Hex, 你必须在需求里指定所接受的依赖的版本。它支持一些基本的操作符,例如``,``,``,``: 175 | 176 | ``` 177 | # Only version 2.0.0 178 | "== 2.0.0" 179 | 180 | # Anything later than 2.0.0 181 | "> 2.0.0" 182 | ``` 183 | 184 | 需求也支持用`and`和`or`表达复杂的情况: 185 | 186 | ``` 187 | # 2.0.0 and later until 2.1.0 188 | ">= 2.0.0 and < 2.1.0" 189 | ``` 190 | 191 | 类似上面的例子非常地常见,所以它也能用简单的方式表达: 192 | 193 | ``` 194 | "~> 2.0.0" 195 | ``` 196 | 197 | 注意为git库设置版本需求不会影响到取出的分支和标签,所以类似下面这样的定义是合法的: 198 | 199 | ``` 200 | { :some_project, "~> 0.5.0", github: "some_project/other", tag: "0.3.0" } 201 | ``` 202 | 203 | 但它会导致一个依赖永远无法满足,因为被取出的标签总不能和需求的版本匹配。 204 | 205 | # 1.4.1 源代码管理(scm) 206 | 207 | Mix的设计就考虑到了支持多种的SCM工具,Hex包是默认,但``和``是可选项。常见的一些选项是: 208 | 209 | * `` - 依赖是一个git版本库,Mix可以来获取和升级。 210 | * `` - 依赖是文件系统中的一个路径 211 | * `` - 如何编译依赖 212 | * `` - 依赖所定义的应用的路径 213 | * `` - 依赖所用的环境(详情在后),默认是``; 214 | 215 | 每个SCM也许支持特有的选项,yi``为例,支持下面这些: 216 | 217 | * `` - 一个可选的索引(提交)用来取出版本库; 218 | * `` - 一个可选的标签用来取出版本库; 219 | * `` - 一个可选的分支用来取出版本库; 220 | * `` - 当为真,在依赖中设置submodule; 221 | 222 | # 1.4.2 编译依赖 223 | 224 | 为了编译依赖,Mix会选择最适合的方式。依赖所包含的文件不同,编译的方式也不一样: 225 | 226 | 1, `` - 直接用Mix的`compile`任务编译依赖; 227 | 2, ``或`` - 用``编译,`DEPS`是Mix会默认安装依赖的文件夹; 228 | 3, `` - 直接`make`; 229 | 230 | 如果编译的代码里没有包含以上的任何,你可以在``选项里直接指定一个命令: 231 | 232 | ``` 233 | {:some_dep, git: "...", compile: "./configure && make"} 234 | ``` 235 | 236 | 如果``被设为`false`, 这一步会被忽略。 237 | 238 | # 1.4.3 可重复性 239 | 240 | 任何一个依赖管理工具的重要特性是可重复性。因此当你初次获取依赖,Mix将创建一个文件``,用来包含每个依赖所取出的索引。 241 | 242 | 当另一个开发者得到这个项目的拷贝,Mix将取出相同的那个索引,保证其他的开发者能“重复”同样的设置。 243 | 244 | 运行``能自动升级锁,用``任务来移除锁。 245 | 246 | # 1.4.4 依赖任务 247 | 248 | Elixir自带了许多用来管理项目依赖的任务: 249 | 250 | * `mix deps` - 列出所有的依赖和它的情况; 251 | * `` - 获取所有可得的依赖 252 | * `` - 编译依赖 253 | * `` - 升级依赖; 254 | * `` - 移除依赖文件; 255 | * `` - 解锁依赖 256 | 用`mix help`来获取更多信息。 257 | 258 | # 1.4.5 依赖的依赖 259 | 260 | 如果你的依赖是一个Mix或rebar的项目,Mix知道如何应付:它将自动获取和处理你的依赖的所有的依赖。然而,如果你的项目中有两个依赖共享了同一个依赖,但它们的SCM信息又无法互相匹配的话,Mix将标明这个依赖是分裂的,并发出警告。要解决而这个问题,你可以在你项目中声明选项``, Mix将根据这个信息来获取依赖。 261 | 262 | # 1.5 伞形项目 263 | 264 | 你是否想过,如果能将几个Mix项目打包在一起,只需一个命令就可以运行各自的Mix任务, 该有多方便?。这种将项目打包在一起使用的情况被称为伞形项目。一个伞形项目可以用下面的命令来创建: 265 | 266 | ``` 267 | $ mix new project --umbrella 268 | ``` 269 | 270 | 这将会创建一个包含以下内容的``文件: 271 | 272 | ``` 273 | defmodule Project.Mixfile do 274 | use Mix.Project 275 | 276 | def project do 277 | [apps_path: "apps"] 278 | end 279 | end 280 | ``` 281 | 282 | 这个``选项指定了子项目将会落户的文件夹。在伞形项目中运行的Mix任务,会对``文件夹中的每一个子项目起作用。例如`mix compile`和`mix test`将编译或测试文件夹下的每一个项目。值得注意的是,伞形项目既不是一个普通的Mix项目,也不是一个OTP应用,也不能修改其中的源码。 283 | 284 | 如果在子项目之间有互相依赖的存在,需要指定它们的顺序,这样Mix才能正确编译。如果项目A依赖于项目B,这个依赖关系必须在项目A的``文件里指定。修改文件``来指定这个依赖: 285 | 286 | ``` 287 | defmodule A.Mixfile do 288 | use Mix.Project 289 | 290 | def project do 291 | [app: :a, 292 | deps_path: "../../deps", 293 | lockfile: "../../mix.lock", 294 | deps: deps] 295 | end 296 | 297 | defp deps do 298 | [{ :b, in_umbrella: true }] 299 | end 300 | end 301 | ``` 302 | 303 | 注意上面的子项目中``和``选项。如果在你的伞形项目的所有子项中都有它们,它们将共享依赖。apps文件夹中的`mix new`将自动用这些预设的选型创建一个项目。 304 | 305 | # 1.6 环境 306 | 307 | Mix有环境的概念,能让一个开发者去基于一个外部的设定来定制编译和其他的选项。默认下,Mix能理解项目三类环境: 308 | 309 | * `dev` - mix任务的默认环境; 310 | * `test` - 用于`mix test`; 311 | * `prod` - 在这个环境下,依赖将被载入和编译; 312 | 313 | 在默认情况下,这些环境的行为没有不同,我们之前看到的所有配置都将会影响到这三个环境。针对某个环境的定制,可以通过访问``来实现: 314 | 315 | ``` 316 | def project do 317 | [deps_path: deps_path(Mix.env)] 318 | end 319 | 320 | defp deps_path(:prod), do: "prod_deps" 321 | defp deps_path(_), do: "deps" 322 | ``` 323 | 324 | Mix默认为`dev`环境(除了测试)。可以通过修改环境变量``来改变环境。 325 | 326 | ``` 327 | $ MIX_ENV=prod mix compile 328 | ``` 329 | 330 | 在下一章,我们将学习如何用Mix编写OTP应用和如何创建你自己的任务。 331 | -------------------------------------------------------------------------------- /mix/Chapter2.md: -------------------------------------------------------------------------------- 1 | # 2 用Mix编写OPT应用 2 | 3 | * [2.1 Stacker服务器](#toc_1) 4 | * [2.1.1 深入学习回调函数](#toc_2) 5 | * [2.1.2 干掉一个服务器](#toc_3) 6 | * [2.2 监控服务器](#toc_4) 7 | * [2.3 谁来监控监工](#toc_5) 8 | * [2.4 启动应用](#toc_6) 9 | * [2.5 配置应用](#toc_7) 10 | 11 | 在Elixir里,我们如何保存状态? 12 | 13 | 我们的软件需要在运行时系统里保存状态,配置,数据。在之前的[章节](http://elixir-lang.org/getting_started/2.html#receive)里我们已学会了如何用进程/Actor保持状态,在一个循环中如何接受以及回复消息,但这种方式似乎不够可靠。如果我们的进程被一个错误退出了怎么办?难道我们真的需要仅仅为一个配置而去创建一个新的进程? 14 | 15 | 在这一章,我们将用OTP的方式来回答这些问题。在实践中,我们不必使用Mix来编写这样的应用,然而借此机会正好让我们了解一些Mix提供的一些方便之处。 16 | 17 | # 2.1 Stacker服务器 18 | 19 | 我们的应用将会是一个运行我们推进/推出的简单堆栈。我们管它叫stacker: 20 | 21 | ``` 22 | $ mix new stacker --bare 23 | ``` 24 | 25 | 我们的应用将包含一个堆栈,允许同一时间被许多的进程访问。为此,我们将创建一个服务器来负责管理这个堆栈。客户端随时可以向服务器发送消息来从服务器推进或取出某物。 26 | 27 | 因为在Erlang和Elixir里,创建这样的一个服务器是常见的一个范式,在OTP里有一个被称为**GenServer**的行为封装了这些常见的服务器功能。让我们创建一个文件`lib/stacker/server.ex`, 这就是我们的第一个服务器: 28 | 29 | ``` 30 | defmodule Stacker.Server do 31 | use GenServer.Behaviour 32 | 33 | def init(stack) do 34 | { :ok, stack } 35 | end 36 | 37 | def handle_call(:pop, _from, [h|stack]) do 38 | { :reply, h, stack } 39 | end 40 | 41 | def handle_cast({ :push, new }, stack) do 42 | { :noreply, [new|stack] } 43 | end 44 | end 45 | ``` 46 | 47 | 我们的服务器定义了三个函数: `init/1`,`handle_call/3`和`handle_cast/2`。我们不会直接调用这些函数,它们是当我们请求服务器的时候由OTP来使用的函数。我们很快会了解详情,现在我们无需关心更多。为此,在你的命令行里运行`iex -S mix`来开始用mix来启动iex,输入下面的指令: 48 | 49 | ``` 50 | # Let's start the server using Erlang's :gen_server module. 51 | # It expects 3 arguments: the server module, the initial 52 | # stack and some options (if desired): 53 | iex> { :ok, pid } = :gen_server.start_link(Stacker.Server, [], []) 54 | {:ok,<...>} 55 | 56 | # Now let's push something onto the stack 57 | iex> :gen_server.cast(pid, { :push, 13 }) 58 | :ok 59 | 60 | # Now let's get it out from the stack 61 | # Notice we are using *call* instead of *cast* 62 | iex> :gen_server.call(pid, :pop) 63 | 13 64 | ``` 65 | 66 | 非常好,我们的服务器工作正常!然而在幕后其实发生了很多的事情,然我们来一一探究。 67 | 68 | 首先,我们用OTP中的[`:gen_server`](http://www.erlang.org/doc/man/gen_server.html)模块发动服务器。注意我们使用了`start_link`, 它启动了服务器并且把当前的进程与之相连。在这种情况下,如果服务器死了,它将会向我们的当前进程发送一个退出消息,使它也退出。我们将在后面看到这个行为。函数`start_link`返回的是新创建的进程的识别符(`pid`)。 69 | 70 | 之后,我们向服务器发送了一个**cast**消息。消息的内容是`{ :push, 13 }`,与我们之前定义在`Stacker.Server`中的回调函数`handle_cast/2`里的一致。无论何时我们发送一个`cast`消息,函数`handle_cast/2`会被调用来处理这个消息。 71 | 72 | 接着,我们最终用发送`call`消息地方时,看到了堆栈里的情况,它触发了回调`handle_call/3`。那么,`cast`和`call`到底有什么不同呢? 73 | 74 | `cast`消息是异步的:我们向服务器发送一个消息,然而并不期待回复。这也是为什么我们的`handle_cast/2 `回调返回的是`{ :noreply, [new|stack] }`的缘故。这个元组中的第一个元素表明了无需回复,而第二个元素是包含了新物件的经过升级的堆栈。 75 | 76 | 相反,`call`消息是同步的。当我们发送一个`call`消息,客户端期待一个回复。在这个例子中,回调`handle_call/3`返回了`{ :reply, h, stack }`,其中第二个元素就是用来返回的内容,而第三个是我们不包含头的堆栈。因为`call`能够向客户端返还消息,所以它的也多了一个有关客户端情况的参数(`_from`)。 77 | 78 | # 2.1.1 深入学习回调函数 79 | 80 | 在GenServer的例子中,类似`handle_call`或`handle_cast`的函数可能返回8种不同的数值: 81 | 82 | ``` 83 | { :reply, reply, new_state } 84 | { :reply, reply, new_state, timeout } 85 | { :reply, reply, new_state, :hibernate } 86 | { :noreply, new_state } 87 | { :noreply, new_state, timeout } 88 | { :noreply, new_state, :hibernate } 89 | { :stop, reason, new_state } 90 | { :stop, reason, reply, new_state } 91 | ``` 92 | 93 | 一个GenServer的实现必须实现6种不同的回调函数。模块`GenServer.Behaviour`自动定义了这些函数,但又允许我们根据需要来修改。下面是这些函数的列表: 94 | 95 | * `init(args) ` - 当服务器启动时调用 96 | * `handle_call(msg, from, state)` - 被调用来处理`call`消息 97 | * `handle_cast(msg, state)` - 被调用来处理`cast`消息 98 | * `handle_info(msg, state)` - 被调用来处理进程所收到的其他消息 99 | * `terminate(reason, state)` - 在服务器当机之前被调用,对清理很有用 100 | * `code_change(old_vsn, state, extra)` - 在应用代码热升级的时候被调用 101 | 102 | # 2.1.2 干掉一个服务器 103 | 104 | Of what use is a server if we cannot crash it? 105 | 106 | 实际上要使一个服务器当机并不难。我们的回调`handle_call/3`只有当堆栈不是空的时候才工作正常(想起来了吗,`[h|t]`不能匹配空列表)。让我们在堆栈为空的情况下发送一个消息看看: 107 | 108 | ``` 109 | # Start another server, but with an initial :hello item 110 | iex> { :ok, pid } = :gen_server.start_link(Stacker.Server, [:hello], []) 111 | {:ok,<...>} 112 | 113 | # Let's get our initial item: 114 | iex> :gen_server.call(pid, :pop) 115 | :hello 116 | 117 | # And now let's call pop again 118 | iex> :gen_server.call(pid, :pop) 119 | 120 | =ERROR REPORT==== 6-Dec-2012::19:15:33 === 121 | ... 122 | ** (exit) 123 | ... 124 | ``` 125 | 126 | 你可以看到,这里有两个错误报告。第一个因为当机由服务器产生。因为服务器是同我们的进程相连的,它也发送了一个退出的消息,`IEx as ** (exit) ....` 127 | 128 | 因为我们的服务器总会崩溃,我们需要监控它们,这也是下面的内容。`GenServer.Behaviour`不仅仅包含我们已经学到的这些。请查看[`GenServer.Behaviour`](http://elixir-lang.org/docs/stable/GenServer.Behaviour.html)的文档来发现更多。 129 | 130 | # 2.2 监控服务器 131 | 132 | 当在Eralng和Elixir里编写应用,一个常用到的哲学是fail first。也许是因为资源不可得,也许是服务之间的超时,或许是其他的什么原因。这也是为什么有能力对这些崩溃作出反应并回复过来,是非常重要的。把这些牢记在心,我们为我们的服务器编写一个supervisor。 133 | 134 | 用下面的内容创建一个文件,`lib/stacker/supervisor.ex`: 135 | 136 | ``` 137 | defmodule Stacker.Supervisor do 138 | use Supervisor.Behaviour 139 | 140 | # A convenience to start the supervisor 141 | def start_link(stack) do 142 | :supervisor.start_link(__MODULE__, stack) 143 | end 144 | 145 | # The callback invoked when the supervisor starts 146 | def init(stack) do 147 | children = [ worker(Stacker.Server, [stack]) ] 148 | supervise children, strategy: :one_for_one 149 | end 150 | end 151 | ``` 152 | 153 | 在监控中,唯一需要实现的回调函数是`init(args)`。这个回调必须返回监工的规格,在上面的例子中实际调用了帮助函数`supervise/2`。 154 | 155 | 我们的监工非常简单:它必须监控我们的工人`Stacker.Server`, 工人的启动需要一个参数,在这是默认的堆栈。完成定义的工人被用`:one_for_one`的策略监控,这意味着每一次工人死亡之后都会被重启。 156 | 157 | 由于我们的工人由`Stacker.Server `模块指定,并且需要传递`stack`作为参数,在默认下,监工将调用函数`Stacker.Server.start_link(stack)`来启动工人,所以让我们来实现它: 158 | 159 | ``` 160 | defmodule Stacker.Server do 161 | use GenServer.Behaviour 162 | 163 | def start_link(stack) do 164 | :gen_server.start_link({ :local, :stacker }, __MODULE__, stack, []) 165 | end 166 | 167 | def init(stack) do 168 | { :ok, stack } 169 | end 170 | 171 | def handle_call(:pop, _from, [h|stack]) do 172 | { :reply, h, stack } 173 | end 174 | 175 | def handle_cast({ :push, new }, stack) do 176 | { :noreply, [new|stack] } 177 | end 178 | end 179 | ``` 180 | 181 | 函数`start_link`同我们之前启动服务器的方式很相似,除了在这里我们需要多传递一个参数`{ :local, :stacker }`。这个参数在本地节点上注册我们的服务器,运行用一个名字(在这里,是`:stacker`)来调用它,而无需直接使用`pid`。 182 | 183 | 不借助监工,让我们运行`iex -S mix`来再一次打开控制台,这也会再一次编译我们的文件: 184 | 185 | ``` 186 | # Now we will start the supervisor with a 187 | # default stack containing :hello 188 | iex> Stacker.Supervisor.start_link([:hello]) 189 | {:ok,<...>} 190 | 191 | # And we will access the server by name since 192 | # we registered it 193 | iex> :gen_server.call(:stacker, :pop) 194 | :hello 195 | ``` 196 | 197 | 注意监工自动为我们启动了服务器,现在我们可以用名字`:stacker`向服务器发送消息了。如果我们使得服务器当机,会发送甚什么? 198 | 199 | ``` 200 | iex> :gen_server.call(:stacker, :pop) 201 | 202 | =ERROR REPORT==== 6-Dec-2012::19:15:33 === 203 | ... 204 | ** (exit) 205 | ... 206 | 207 | iex> :gen_server.call(:stacker, :pop) 208 | :hello 209 | ``` 210 | 211 | 它和前面一样当机了,当监工立刻用默认的堆栈重启了它,使得我们可以再一次接受到`:hello`。太棒了! 212 | 213 | 默认下,监工运行一个工人在5秒内最多当机5次。如果工人当机的频率超过了这个限制,监工就会放弃它,不在重启。让我们尝试接连发送一个未知的消息看看(要快!): 214 | 215 | ``` 216 | iex> :gen_server.call(:stacker, :unknown) 217 | ... 5 times ... 218 | 219 | iex> :gen_server.call(:stacker, :unknown) 220 | ** (exit) {:noproc,{:gen_server,:call,[:stacker,:unknown]}} 221 | gen_server.erl:180: :gen_server.call/2 222 | ``` 223 | 224 | 第六个消息不在产生错误报告,因为我们的服务器不在自动重启了。Elixir返回`:noproc`(`no process`的简写形式),意味着那里已经没有一个被称为`:stacker`的进程了。重启的次数和时间间隔,可以通过向函数`supervise`传递参数进行修改。除了上面例子中的`:one_for_one`, 监工也能选用不同的重启策略。如果向知道还有那些支持的策略,检查`Supervisor.Behaviour`的[文档](http://elixir-lang.org/docs/stable/Supervisor.Behaviour.html)。 225 | 226 | # 2.3 谁来监控监工? 227 | 228 | 我们已经编写了我们的监工,但有一个问题:谁来监控监工?为了回答这个问题,OTP有一个概念,应用(application)。应用可以被作为一个整体启动或关闭,当这些发生的时候,它们通常和一个监工相连。 229 | 230 | 在之前的章节中,我们已经看到了Mix如何用文件`mix.exs`中的`application`函数中包含的信息来自动地产生一个`.app`文件,每次编译我们的项目。 231 | 232 | 这个`.app`文件被称为应用规格,它必须包含我们的应用的依赖,它定义的模块,注册名和其他的许多。其中的一些信息由Mix来自动完成,但另外的一些数据需要手动添加。 233 | 234 | 在我们的例子里面,我们的应用有一个监工,而且它用名字`:stacker`注册了监工。也就是说,为了避免冲突,我们需要在应用规格里加入所有的注册名。如果两个应用注册了同一个名字,我们能更快地找到冲突。所以,让我们打开文件``,用下面的内容编辑`application`函数: 235 | 236 | ``` 237 | def application do 238 | [ registered: [:stacker], 239 | mod: { Stacker, [:hello] } ] 240 | end 241 | ``` 242 | 243 | 在`:registered`键中我们指定了我们的应用注册的所有名字。而`:mod`键的用途是,一旦应用启动,它必须去调用应用模块回调函数(appliation module callback)。在我们的例子里面,应用模块回调函数是`Stack`模块,而且它将会接受到默认的参数,堆栈`[:hello]`。这个回调函数必须返回同应用相关的监工的`pid`。 244 | 245 | 有了这些在心里,让我们打开文件`lib/stacker.ex`,添加以下的内容: 246 | 247 | ``` 248 | defmodule Stacker do 249 | use Application.Behaviour 250 | 251 | def start(_type, stack) do 252 | Stacker.Supervisor.start_link(stack) 253 | end 254 | end 255 | ``` 256 | 257 | `Application.Behaviour`期待两个回调,`start(type, args)`和`stop(state)`。我们需要来实现`start(type, args)`, 而`stop(state)`可以先放在一边不管。 258 | 259 | 在添加了上面的应用行为之后,你只需要在一次启动`iex -S mix`。我们的文件将被再一次重编译,监工(包括我们的服务器)会自动启动: 260 | 261 | ``` 262 | iex> :gen_server.call(:stacker, :pop) 263 | :hello 264 | ``` 265 | 266 | 太棒了,它能性!也许你已经注意到了,应用回调函数`start/2`接受了一个类型参数,虽然我们忽略了它。这个类型控制当我们的监工,自然也包括应用,崩溃的时候,虚拟机应该如何应对。你可以通过阅读`Application.Behaviour`的[文档](http://elixir-lang.org/docs/stable/Application.Behaviour.html)学到更多。 267 | 268 | 269 | 最后,注意`mix new`支持一个`--sup`选项,它告诉Mix产生一个包含应用模块回调的监工,自动地完成了一些上面的工作。你一定要试试! 270 | 271 | # 2.4 启动应用 272 | 273 | 在任何时候,我们都不必自己去启动我们定义的应用。那是因为默认下Mix会启动所有的应用,包括所依赖的应用。我们能通过调用OTP提供的[`:application`模块](http://www.erlang.org/doc/man/application.html)中的函数来手动地启动应用: 274 | 275 | ``` 276 | iex> :application.start(:stacker) 277 | { :error, { :already_started, :stacker } } 278 | ``` 279 | 280 | 在上面的例子中,因为应用已经事先启动了,它返回了一个错误信息。 281 | 282 | Mix不仅启动你的应用,而且包括所有的你的应用的依赖。请注意你的项目的依赖(我们在前面几章中讨论过的,定义在键`deps`里的)和应用依赖是不同的。 283 | 284 | 项目依赖也许会包含你的测试框架或者一个编译时的依赖。应用依赖是运行时你的应用依赖的一切。一个应用依赖需要明确地被加入`appliation`函数里: 285 | 286 | ``` 287 | def application do 288 | [ registered: [:stacker], 289 | applications: [:some_dep], 290 | mod: { Stacker, [:hello] } ] 291 | end 292 | ``` 293 | 294 | 当在Mix里运行任务,它将确保应用以及应用的依赖都会被启动。 295 | 296 | # 2.5 配置应用 297 | 298 | 除了我们已经看到的键`:registered`,`:applications`和`:mod`,应用也支持被读取和设置的配置数值。 299 | 300 | 在命令行里,尝试: 301 | 302 | ``` 303 | iex> :application.get_env(:stacker, :foo) 304 | :undefined 305 | iex> :application.set_env(:stacker, :foo, :bar) 306 | :ok 307 | iex> :application.get_env(:stacker, :foo) 308 | { :ok, :bar } 309 | ``` 310 | 311 | 这个机制非常有用,它无需创建一整个监工链就能为你的应用提供配置数值。应用的默认的配置数值可以通过如下的方式在文件`mix.exs`中定义: 312 | 313 | ``` 314 | def application do 315 | [ registered: [:stacker], 316 | mod: { Stacker, [:hello] }, 317 | env: [foo: :bar] ] 318 | end 319 | ``` 320 | 321 | 现在,从控制台里退出,然后用`iex -S mix`重启它: 322 | 323 | ``` 324 | iex> :application.get_env(:stacker, :foo) 325 | { :ok, :bar } 326 | ``` 327 | 328 | 例如,IEx和ExUnit是两个Elixir包含的应用,它们的`mix.exs`文件就是使用了这样的配置,文件[IEx](https://github.com/elixir-lang/elixir/blob/master/lib/iex/mix.exs)和[ExUnit](https://github.com/elixir-lang/elixir/blob/master/lib/ex_unit/mix.exs)。这样的应用于是提供了提供了[对读写这些数值的封装](https://github.com/elixir-lang/elixir/blob/d2bfd10299dc0e2df573589f1d33565177d63a7d/lib/ex_unit/lib/ex_unit.ex#L125-L155)。 329 | 330 | 到这里,我们结束了这一章。我们已经学习了如何创建服务器,监控它们,把它们和我们的应用整合和提供简单的配置选项。在下一章,我们将学习如何创建一个Mix中的定制任务。 331 | --------------------------------------------------------------------------------