基本种类

在本章中,我们将学习更多关于 Elixir 基本类型的知识:整数、浮点数、布尔值、原子、字符串、列表和元组。一些基本类型是:

iex> 1          # integer
iex> 0x1F       # integer
iex> 1.0        # float
iex> true       # boolean
iex> :atom      # atom / symbol
iex> "elixir"   # string
iex> [1, 2, 3]  # list
iex> {1, 2, 3}  # tuple

基本算法

打开iex并键入以下表达式:

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

注意10 / 2返回一个 float 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> 0o777
511
iex> 0x1F
31

浮点数需要一个点,后面至少有一个数字,同时也支持e科学记数法:

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

Elixir 中的浮点数是64位双精度。

您可以调用round函数来获取给定浮点数的最接近的整数,或者trunc获取浮点数整数部分的函数。

iex> round(3.58)
4
iex> trunc(3.58)
3

定义函数

Elixir 中的函数由他们的函数名称和携带参数数量来确定。函数的参数数量描述函数携带的参数的数量。从这一点开始,我们将使用函数名称和参数个数描述整个文档中的函数。round/1定义了函数名字为round,并且携带了1个参数,round/2 定义了一个和round名字相同的函数,但是参数的个数却是2个

布尔

Elixir 支持truefalse作为布尔值:

iex> true
true
iex> true == false
false

Elixir 提供了一些谓词函数来检查值类型。例如,该is_boolean/1函数可用于检查值是否为布尔值:

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

你也可以使用is_integer/1is_float/1is_number/1检查,分别,如果一个参数是一个整数,浮点数,或任一。

注意:在任何时候,您都可以在 shell 中输入h()以打印有关如何使用 shell 的信息。h帮助程序也可用于访问任何功能的文档。例如,输入h is_integer/1将打印该is_integer/1功能的文档。它也适用于操作符和其他构造(尝试h ==/2)。

原子

原子是一个常数,其名称是它自己的值。其他一些语言称这些符号为:

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

布尔truefalse实际上是原子:

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

最后,Elixir 有一个名为别名的构造,我们将在后面进行探讨。以大写字母开头的别名也是原子:

iex> is_atom(Hello)
true

字符串

Elixir 中的字符串用双引号分隔,并以 UTF-8 编码:

iex> "hellö"
"hellö"

注意:如果你在 Windows 上运行,你的终端有可能默认不使用 UTF-8。您可以chcp 65001在输入 IEx 之前运行,更改当前会话的编码。

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

请注意,该字符串中的字节数是6,即使它有5个字符。这是因为字符“ö”需要2个字节以 UTF-8 表示。我们可以通过使用下面的String.length/1函数,根据字符的数量得到字符串的实际长度:

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

字符串模块包含一组上作为 Unicode 标准定义的字符串操作的功能:

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

匿名函数

匿名函数可以内联创建并由关键字fnend分隔:

iex> add = fn a, b -> a + b end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> add.(1, 2)
3
iex> is_function(add)
true
iex> is_function(add, 2) # check if add is a function that expects exactly 2 arguments
true
iex> is_function(add, 1) # check if add is a function that expects exactly 1 argument
false

函数是 Elixir 中的“头等公民”,意思是它们可以像整数和字符串一样作为参数传递给其他函数。在这个例子中,我们已经将变量add中的is_function/1函数传递给正确返回true的函数。我们也可以通过调用来检查函数的参数is_function/2

请注意,.变量和括号之间的点()需要调用匿名函数。点确保调用名为匿名函数addadd/2命名函数之间不存在歧义。从这个意义上说,Elixir 明确区分了匿名函数和命名函数。我们将在第8章探讨这些差异。

匿名函数是闭包,因此当函数被定义时,它们可以访问范围内的变量。让我们定义一个新的匿名函数,它使用add我们之前定义的匿名函数:

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

请记住,函数内部分配的变量不会影响其周围的环境:

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

(链接)列表

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]

列表操作符从不修改现有列表。连接到或从列表中删除元素将返回一个新列表。我们说 Elixir 的数据结构是不可变的。不变性的一个优点是它导致更清晰的代码。您可以随意传递数据并保证没有人会改变它 - 只能对其进行转换。

在整个教程中,我们将讨论很多关于列表的首尾的内容。头是列表的第一个元素,尾部是列表的其余部分。它们可以通过函数hd/1tl/1。让我们给列表分配一个变量并检索它的头部和尾部:

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

获取空列表的头或尾会引发一个错误:

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

有时你会创建一个列表,它将返回一个单引号的值。例如:

iex> [11, 12, 13]
'\v\f\r'
iex> [104, 101, 108, 108, 111]
'hello'

当Elixir看到可打印的 ASCII 码列表时,Elixir 将把它打印为charlist(字面上是一个字符列表)。Charlists 在与现有的 Erlang 代码交互时非常常见。无论何时您在 IEx 中看到一个值,并且您不确定它是什么,您都可以使用它i/1来检索有关它的信息:

iex> i 'hello'
Term
  'hello'
Data type
  List
Description
  ...
Raw representation
  [104, 101, 108, 108, 111]
Reference modules
  List

请记住,Elixir 中的单引号和双引号表示不同,因为它们由不同类型表示:

iex> 'hello' == "hello"
false

单引号是 charlists,双引号是字符串。我们将在“二进制文件,字符串和 charlists”一章中详细讨论它们。

元组

Elixir 使用大括号来定义元组。像列表一样,元组可以保存任何值:

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

元组将元素连续存储在内存中。这意味着通过索引访问元组元素或获取元组大小是一种快速操作。索引从零开始:

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

也可以将元素放在元组中的特定索引处put_elem/3

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

注意put_elem/3返回一个新的元组。存储在tuple变量中的原始元组未被修改。像列表一样,元组也是不可变的。对元组的每一个操作都会返回一个新的元组,它永远不会改变给定的元组。

列表或元组?

列表和元组有什么区别?

列表作为链接列表存储在内存中,这意味着列表中的每个元素都保存其值并指向下一个元素,直到到达列表的末尾。这意味着访问列表的长度是一个线性操作:我们需要遍历整个列表以便找出它的大小。

同样,列表级联的性能取决于左侧列表的长度:

iex> list = [1, 2, 3]

# This is fast as we only need to traverse `[0]` to prepend to `list`
iex> [0] ++ list
[0, 1, 2, 3]

# This is slow as we need to traverse `list` to append 4
iex> list ++ [4]
[1, 2, 3, 4]

另一方面,元组连续存储在内存中。这意味着获取元组大小或按索引访问元素的速度很快。然而,为元组更新或添加元素是很昂贵的,因为它需要在内存中创建一个新的元组:

iex> tuple = {:a, :b, :c, :d}
iex> put_elem(tuple, 2, :e)
{:a, :b, :e, :d}

请注意,这仅适用于元组本身,而不适用于其内容。例如,当你更新一个元组时,除了已被替换的条目外,所有条目都在旧元组和新元组之间共享。换句话说,Elixir 中的元组和列表能够分享他们的内容。这减少了语言需要执行的内存分配量,并且只能归功于语言的不可变语义。

这些性能特征决定了这些数据结构的使用。元组的一个非常常见的用例是使用它们从函数返回额外的信息。例如,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元素,其中atom 为第一个元素,文件内容为第二个元素。否则,它返回一个元组:error和错误描述。

大多数时候,Elixir 会引导你做正确的事情。例如,有一个elem/2函数可以访问一个元组项,但是没有内建的列表等价物:

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

在对数据结构中的元素进行计数时,Elixir 还遵循一个简单的规则:size如果操作的时间是恒定的(即,该值是预先计算的),或者length操作是线性的(即计算长度变慢随着输入的增长)。作为助记符,“length”和“linear”都以“l”开始。

例如,到目前为止,我们已经使用了4个计数函数:( byte_size/1对于字符串中的字节数),tuple_size/1(对于元组大小),length/1(对于列表长度)和String.length/1(对于字符串中的字符数)。我们byte_size用来获取字符串中的字节数 - 一个便宜的操作。另一方面,检索 Unicode 字符的数量使用String.length,并且可能是昂贵的,因为它依赖遍历整个字符串。

Elixir 还提供PortReferencePID作为数据类型(通常用于过程交流),我们将在谈论过程时快速浏览它们。现在,我们来看看一些基本类型的基本操作符。