1

Elixir Getting Started 1 交互式Elixir

2 Basic types

在这章中我们将学习Elixir中的一些基础的数据类型:整数(integers),浮点数(floats),原子(atoms),列表(lists)和字符串(strings)。它们是:

iex> 1            # 整数
iex> 0x1F         # 整数
iex> 1.0          # 浮点数
iex> :atom        # 原子 / 符号
iex> "elixir"     # 字符串
iex> [1, 2, 3]    # 列表
iex> {1, 2, 3}    # 元组

2.1 基本运算

打开iex, 输入下面的表达式:

iex> 1 + 2
3
iex> 5 * 5
25
iex> 10 / 2
5.0

注意10 / 2 返回 浮点数 5.0而不是整型5,这是合理的,因为在Elixir中,操作符/总是返回浮点数。如果你想得到整数或是求余,请使用divrem函数:

iex> div(10, 2)
5
iex> div 10, 2
5
iex> rem 10, 3
1

你可能注意到了,在Elixir中调用函数时圆括号并不是必须的。
Elixir还支持方便的输入二进制,八进制和十六进制数:

iex> 0b1010
10
iex> 0777
511
iex> 0x1F
31

浮点数需要在一个点之后跟随至少一位数字,同时也支持指数e的形式:

iex> 1.0
1.0
iex> 1.0e-10
1.0e-10

在Elixir中,浮点类型都是64位双精度。

2.2 布尔值 Booleans

Elixir支持两种布尔值,true和false。

iex> true
true
iex> true == false
false

Elixr提供了一些断言(predicate function)函数用来检查值的类型。比如,函数is_boolean/1能被用来检查一
个值是不是布尔值:

注意:Elixir中的函数定义是包括函数名和参数个数。例如 is_boolean/1 表示函数名是is_boolean并有一个函
数参数的函数。is_boolean/2 是另一个新的函数,虽然有相同的名字,但是参数个数不一样。

iex> is_boolean(true)
true
iex> is_boolean(1)
false

你也可以用is_integer/1来检查参数是否是整数,用is_float/1检查参数是否是浮点数或者用is_number/1检查参数是否整型或者浮点数。

注意:在任何时候你都可以在控制台中键入h来获取帮助信息。h函数也能被用来访问函数的文档。例如,输入h is_integer/1 会打印出函数is_integer/1的文档。它也能用在操作符和其他的结构上(试试h ==/2)。

2.3 原子 Atoms

一个原子的值就是它本身,别的语言也有称符号的(symbol)

iex> :hello
:hello
iex> :hello == :world
false

事实上,布尔值truefalse就是原子:

iex> true == :true
true
iex> is_atom(false)
true

2.4 字符串 Strings

在Elixir中字符串必须用双引号来表达,并且用UTF-8来编码:

iex> "hellö"
"hellö"

Elixir也支持字符串内嵌写法:

iex> "hellö #{:world}"
"hellö world"

字符串可以包括换行,也可以使用转义字符引入换行:

iex> "hello
...> world"
"hello\nworld"
iex> "hello\nworld"
"hello\nworld"

你能用IO模块中的函数IO.puts/1来打印字符串:

iex> IO.puts "hello\nworld"
hello
world
:ok

注意:IO.puts/1函数在打印完成后返回原子:ok
字符串在Elixir内部是用二制类型(字节序列)来的表示:

iex> is_binary("hellö")
true

我们也可以获取字符串有多少个字节:

iex> byte_size("hellö")
6

字符串hellö的字节数书6,即使它只有5个字符。因为字符 "ö" 在UTF-8编码中占用了两个字节。可以使用
String.length/1获取字符串的字符数。

iex> String.length("hellö")
5

String module 模块包含了一系列unicode标准中定义胡函数。

iex> String.upcase("hellö")
"HELLÖ"

请牢记单引号双引号 是不一样的,在Elixir中代表了不同的类型。

iex> 'hellö' == "hellö"
false

我们会在"Binaries, strings and char lists" 这一章介绍更多对unicode的支持和单引号和双引号之间的不同。

2.5 匿名函数 Anonymous functions

使用fnend关键字定义匿名函数:

iex> add = fn a, b -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> is_function(add)
true
iex> is_function(add, 2)
true
iex> is_function(add, 1)
false
iex> add.(1, 2)
3

在Elixir中函数是“第一等公民”,这意味着它们能被想整数和字符串一样当成参数传给别的函数。在上面的例子里,我们把变量add 指向的函数传给了另一个函数is_function/1,并且返回了结果true。我们也能用is_function/2来检查一个函数的元数(arity,参数数量)

注意当调用一个匿名函数时,在指向这个匿名函数的变量名和圆括号之间需要有一个点号(.)

匿名函数本身也是一个闭包,因此它们能够访问在被定义时的同作用域的其他变量:

iex> add_two = fn a -> add.(a, 2) end
#Function<6.71889879/1 in :erl_eval.expr/5>
iex> add_two.(2)
4

记住函数内的赋值不会影响它周围的环境:

iex> x = 42
42
iex> (fn -> x = 0 end).()
0
iex> x
42

2.6 列表 (Linked) Lists

Elixir用方括号来表示一个列表,列表内元素可以是任意类型:

iex> [1, 2, true, 3]
[1, 2, true, 3]
iex> length [1, 2, 3]
3

两个列表可以使用列表添加 ++/2和移除/2`操作符进行合并和移除

iex> [1, 2, 3] ++ [4, 5, 6]
[1, 2, 3, 4, 5, 6]
iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]

贯穿整个教程,我们会不停地谈到列表的头(head)和尾(tail)。头是列表中的第一个元素,尾是剩下的。它们可以被分别用函数hd/1tl/1得到。让我们创建一个列表,试着获取头和尾:

iex> list = [1,2,3]
iex> hd(list)
1
iex> tl(list)
[2, 3]

尝试获取空列表的头或尾会导致错误:

iex> hd []
** (ArgumentError) argument error

2.7 元组Tuples

Elixir用花括号来表示元组。和列表一样,元组能包含任何类型的元素:

iex> {:ok, "hello"}
{:ok, "hello"}
iex> size {:ok, "hello"}
2

元组中在内存中的存储是连续的。也就是说,用索引来访问元组中的元素或者获取元组的大小这样的操作是非常快的。(元组的索引是从0开始的):

iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"
iex> tuple_size(tuple)
2

也可以使用set_elem/3函数给指定索引设置新值:

iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> set_elem(tuple, 1, "world")
{:ok, "world"}
iex> tuple
{:ok, "hello"}

注意set_elem/3返回一个新的元组。因为在Elixri中的数据类型都是不可变的,所以旧的元组并没有变化。变量不可变带来的一个好处是,它使得Elixir的代码变得相对简单,因为你不需要担心有一些代码在特别的地方修改了你的数据。

同时,不可变量也对避免一些常见的问题有帮助,比如并行代码中的竞态条件, 当两个以上的实体在同一时间试图修
改同一个数据结构。

2.8 列表还是元组

列表和元组的不同之处在哪里?

列表在内存中是以链表的形式存储。这意味着列表中的每一个元素都指向下一个元素,直到到达列表的最后。我们把每一个列表单位叫做一个cons单元.

iex> list = [1|[2|[3|[]]]]
[1, 2, 3]

这也意味着获取一个列表的长度是一个线性操作:我们必须遍历整个列表来弄清楚列表的大小。把新元素插入列表头部的方式来更新列表是一个比较快的操作:

iex> [0] ++ list
[0, 1, 2, 3]
iex> list ++ [4]
[1, 2, 3, 4]

在上面的例子中,第一个操作是比较块的,因为我们只是加了一个指向list的新的cons单元。第二个例子就比较慢了,因为添加一个新的元素到尾部我们必须重建整个列表。

从另一方面来说,元组在内存的存储是连续的。这意味着获取元组的大小或通过索引来访问元素是非常快的。然而,修改或增减元组中的元素是非常昂贵的操作,因为它需要在内存中复制整个元组。

这些性能特征决定了这些数据结构的使用场景。一个常见的例子是函数返回一个元组来表示附加的信息。比如,File.read/1用来读取一个文件的内容,它返回的就是一个元组:

iex> File.read("path/to/existing/file")
{:ok, "... contents ..."}
iex> File.read("path/to/unknown/file")
{:error, :enoent}

File.read/1读取的路径存在的话,返回的元组第一个元素是原子:ok,第二个元素是文件的内容。否则,会返回一个元组,元组内容是:error和错误原因。

大多数的时候,Elixir会引导你做正确的事。例如,有一个elem/2函数来访问元组内的元素,但列表没有内建此函数

iex> tuple = {:ok, "hello"}
{:ok, "hello"}
iex> elem(tuple, 1)
"hello"

在Elixir中,当我们计算一个数据结构的大小时,需要遵循一个简单的原则:当这个操作所需的时间是一个常量时间(也就是说,这个数值是事先已经计算好了的)的时候,函数应该用size来命名,如果需要显示的计算应该用length

例如,迄今我们遇到过4个获取长度的函数:byte_size/1(获取字符串中的字节数),tuple_size/1(获取元组的大小),length/1(获取列表的大小),String.length/1(获取字符串中的字符数)。可见,当我们用byte_size/1去获得字符串中的字节数的时候,这个操作是相当廉价的,但当我们用String.length/1得到其中的unicode字符的数量时是非常昂贵的操作,因为我们需要历遍整个字符串。

Elixir同时也支持其他的一些数据类型,PortReperencePID(用于进程间的通讯)。当讲到进程相关的章节的时候,我们会具体谈到它们。


lidashuang
6.7k 声望165 粉丝

$ Ruby/Elixir/Golang