本章涵盖:
- 终端交互
- 变量
- 组织代码
- 理解类型系统
- 操作符
- 理解运行时
本章讲述基本的模块、函数和类型系统。
开始前,确保Elixir版本为1.6以上、Erlang版本为20以上。可以参考官方文档安装Erlang和Elixir,也可以使用 asdf-vm/asdf 来安装。
With that out of the way, let’s start the tour of Elixir. The first thing you should know about is the interactive shell.
更多参考
- 语法基础可以参考官方文档: https://elixir-lang.org/getting-started/introduction.html.
- 在网上可以找到更详细的参考文档: https://hexdocs.pm/elixir.
- 可以在论坛提问(https://elixirforum.com/), IRC上的 #elixir-lang 频道 (irc://irc.freenode.net/elixir-lang), 或者 Slack 频道 (https://elixir-slackin.herokuapp.com/).
- Erlang 文档: http://www.erlang.org/doc. 如果不熟悉Erlang语法,你可能需要阅读 Elixir’s crash course on Erlang (https://elixir-lang.org/crash-course.html).
2.1 终端交互
在终端中输入命令 iex
:
$ iex
Erlang/OTP 20 [erts-9.1] [source] [64-bit] [smp:8:8] [ds:8:8:10]
[async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.0) - press Ctrl+C to exit
(type h() ENTER for help)
iex(1)>
接着可以运行Elixir表达式:
iex(1)> 1 + 2
3
NOTE
Elixir中的一切都是表达式返回的值。不仅仅包含函数,也包含类似if
和case
的结构。
最快的退出方式是按两次 Ctrl + C
,如果想用更优雅的方式退出,可以调用 System.halt
。
可以在终端做很多事情,可以键入 h
获取基本的帮助:
iex(4)> h
可以查看某个模块的文档:
iex(5)> h IEx
可以在线查阅同样的文档: https://hexdocs.pm/iex。
接下来,我们开始理解变量。
2.2 变量
iex(1)> monthly_salary = 10000
10000
iex(2)> monthly_salary
10000
iex(3)> monthly_salary * 12
120000
2.3 组织代码
2.3.1 模块
模块是函数的集合,类似命名空间。每一个Elixir函数必须被定义在模块中。
Elixir标准库提供了很多有用的模块。比如 IO
模块能用于 I/O 操作。IO
中的puts
函数用于在屏幕上打印消息。
iex(1)> IO.puts("Hello World!")
Hello World!
:ok
调用模块终的函数,语法是这样 ModuleName.function_name(args)
。
使用 defmodule
定义模块。在模块中,使用 def
定义函数。
defmodule Geometry do
def rectangle_area(a, b) do
a * b
end
end
将以上代码保持为 geometry.ex
文件:
$ iex geometry.ex
iex(1)> Geometry.rectangle_area(6, 7)
42
很简单! 我们创建了一个 Geometry
模块,加载到终端会话,并用于计算矩形的面积。
源文件中可以包含多个模块:
defmodule Module1 do
...
end
defmodule Module2 do
...
end
模块名称必须遵循一定的规则。它始于一个大写字母,通常为驼峰风格。模块名称可以包含 #
,_
, .
字符。后者常用于组织模块层级:
defmodule Geometry.Rectangle do
...
end
defmodule Geometry.Circle do
...
end
嵌套模块:
defmodule Geometry do
defmodule Rectangle do
...
end
...
end
2.3.2 函数
函数通常以小写字母或者下划线开头,几个单词之间可以用下划线连接。
函数名可以用 ?
、!
结束,?
通常表示该函数返回布尔型的 true 或 false,!
表示函数会运行时出错。
defmodule Geometry do
def rectangle_area(a, b) do
...
end
end
不带参数的函数可以 省略括号:
defmodule Program do
def run do
...
end
end
将函数放在一行的写法:
defmodule Geometry do
def rectangle_area(a, b), do: a * b
end
运行函数:
$ iex geometry.ex
iex(1)> Geometry.rectangle_area(3, 2)
6
当然,我们也可以将函数返回的结果存储到变量:
iex(2)> area = Geometry.rectangle_area(3, 2)
6
iex(3)> area
6
在Elixir中,括号也是可选的,但并不推荐这样做:
iex(4)> Geometry.rectangle_area 3, 2
6
如果函数在相同的模块,调用的时候可以省略模块名:
defmodule Geometry do
def rectangle_area(a, b) do
a * b
end
def square_area(a) do
rectangle_area(a, a)
end
end
Elixir内置的管道操作符 |>
,可以很方便的使用:
iex(5)> -5 |> abs() |> Integer.to_string() |> IO.puts()
5
意思等价于:
iex(6)> IO.puts(Integer.to_string(abs(-5)))
5
以下两个表达是等价的:
prev(arg1, arg2) |> next(arg3, arg4)
next(prev(arg1, arg2), arg3, arg4)
增加管道操作符的可读性:
-5
|> abs()
|> Integer.to_string()
|> IO.puts()
2.3.3 函数参数
defmodule Rectangle do
def area(a, b) do
...
end
end
上面的函数有两个参数,在Elixir中通常称为 Rectangle.area/2
,/2
表示参数数量。为何强调这一点,因为函数名相同的函数,但是参数数量不同,就表示不同的函数:
defmodule Rectangle do
def area(a), do: area(a, a)
def area(a, b), do: a * b
end
默认参数:
defmodule Calculator do
def sum(a, b \\ 0) do
a + b
end
end
defmodule MyModule do
def fun(a, b \\ 1, c, d \\ 2) do
a + b + c + d
end
end
2.3.4 函数可见性
defmodule TestPrivate do
def double(a) do
sum(a, a)
end
defp sum(a, b) do
a + b
end
end
iex(1)> TestPrivate.double(3)
6
iex(2)> TestPrivate.sum(3, 4)
** (UndefinedFunctionError) function TestPrivate.sum/2
...
2.3.5 导入和别名
defmodule MyModule do
import IO
def my_function do
puts "Calling imported function."
end
end
defmodule MyModule do
alias IO, as: MyIO
def my_function do
MyIO.puts("Calling imported function.")
end
end
defmodule MyModule do
alias Geometry.Rectangle, as: Rectangle
def my_function do
Rectangle.area(...)
end
end
defmodule MyModule do
alias Geometry.Rectangle
def my_function do
Rectangle.area(...)
end
end
别名可以减少混淆,特别是要从名字很长的模块中调用函数。
2.3.6 模块属性
iex(1)> defmodule Circle do
@pi 3.14159
def area(r), do: r*r*@pi
def circumference(r), do: 2*r*@pi
end
iex(2)> Circle.area(1)
3.14159
iex(3)> Circle.circumference(1)
6.28318
默认模块属性 @moduledoc
、@doc
:
defmodule Circle do
@moduledoc "Implements basic circle functions"
@pi 3.14159
@doc "Computes the area of a circle"
def area(r), do: r*r*@pi
@doc "Computes the circumference of a circle"
def circumference(r), do: 2*r*@pi
end
iex(1)> Code.get_docs(Circle, :moduledoc)
{1, "Implements basic circle functions"}
iex(2)> h Circle
Circle
Implements basic circle functions
iex(3)> h Circle.area
def area(r)
Computes the area of a circle
Type specifications
defmodule Circle do
@pi 3.14159
@spec area(number) :: number
def area(r), do: r*r*@pi
@spec circumference(number) :: number
def circumference(r), do: 2*r*@pi
end
2.3.7 注释
# This is a comment
a = 3.14 # so is this
2.4 了解系统类型
2.4.1 Number
整数、浮点数
iex(1)> 3
3
iex(2)> 0xFF
255
iex(3)> 3.14
3.14
iex(4)> 1.0e-2
0.01
支持标准的计算符
iex(5)> 1 + 2 * 3
7
iex(6)> 4/2
2.0
iex(7)> 3/2
1.5
iex(8)> div(5,2)
2
iex(9)> rem(5,2)
1
iex(10)> 1_000_000
1000000
iex(11)> 999999999999999999999999999999999999999999999999999999999999
999999999999999999999999999999999999999999999999999999999999
2.4.2 Atom
:an_atom
:another_atom
:"an atom with spaces"
variable = :some_atom
别名
省略冒号,以大写开头:
AnAtom
:"Elixir.AnAtom":
iex(1)> AnAtom == :"Elixir.AnAtom"
true
iex(2)> AnAtom == Elixir.AnAtom
true
iex(3)> alias IO, as: MyIO
iex(4)> MyIO.puts("Hello!")
Hello!
iex(5)> MyIO == Elixir.IO
true
Atoms as booleans
iex(1)> :true == true
true
iex(2)> :false == false
true
iex(1)> true and false
false
iex(2)> false or true
true
iex(3)> not false
true
iex(4)> not :an_atom_other_than_true_or_false
** (ArgumentError) argument error
Nil and truthy values
iex(1)> nil == :nil
true
iex(1)> nil || false || 5 || true
5
iex(1)> true && 5
5
iex(2)> false && 5
false
iex(3)> nil && 5
nil
read_cached || read_from_disk || read_from_database
database_value = connection_established? && read_data
2.4.3 Tuples
iex(1)> person = {"Bob", 25}
{"Bob", 25}
iex(2)> age = elem(person, 1)
25
iex(3)> put_elem(person, 1, 26)
{"Bob", 26}
iex(4)> person
{"Bob", 25}
iex(5)> older_person = put_elem(person, 1, 26)
{"Bob", 26}
iex(6)> older_person
{"Bob", 26}
iex(7)> person = put_elem(person, 1, 26)
{"Bob", 26}
2.4.4 Lists
iex(1)> prime_numbers = [2, 3, 5, 7]
[2, 3, 5, 7]
iex(2)> length(prime_numbers)
4
iex(3)> Enum.at(prime_numbers, 3)
7
iex(4)> 5 in prime_numbers
true
iex(5)> 4 in prime_numbers
false
iex(6)> List.replace_at(prime_numbers, 0, 11)
[11, 3, 5, 7]
iex(7)> new_primes = List.replace_at(prime_numbers, 0, 11)
[11, 3, 5, 7]
iex(8)> prime_numbers = List.replace_at(prime_numbers, 0, 11)
[11, 3, 5, 7]
iex(9)> List.insert_at(prime_numbers, 3, 13)
[11, 3, 5, 13, 7]
iex(10)> List.insert_at(prime_numbers, -1, 13)
[11, 3, 5, 7, 13]
iex(11)> [1, 2, 3] ++ [4, 5]
[1, 2, 3, 4, 5]
Recursive list definition

a_list = [head | tail]
iex(1)> [1 | []]
[1]
iex(2)> [1 | [2 | []]]
[1, 2]
iex(3)> [1 | [2]]
[1, 2]
iex(4)> [1 | [2, 3, 4]]
[1, 2, 3, 4]
iex(1)> [1 | [2 | [3 | [4 | []]]]]
[1, 2, 3, 4]
iex(1)> hd([1, 2, 3, 4])
1
iex(2)> tl([1, 2, 3, 4])
[2, 3, 4]
iex(1)> a_list = [5, :value, true]
[5, :value, true]
iex(2)> new_list = [:new_element | a_list]
[:new_element, 5, :value, true]
2.4.5 不可变性 Immutability
a_tuple = {a, b, c}
new_tuple = put_elem(a_tuple, 1, b2)


修改列表


2.4.6 Maps
Dynamically sized maps
iex(1)> empty_map = %{}
iex(2)> squares = %{1 => 1, 2 => 4, 3 => 9}
iex(3)> squares = Map.new([{1, 1}, {2, 4}, {3, 9}])
%{1 => 1, 2 => 4, 3 => 9}
iex(4)> squares[2]
4
iex(5)> squares[4]
nil
iex(6)> Map.get(squares, 2)
4
iex(7)> Map.get(squares, 4)
nil
iex(8)> Map.get(squares, 4, :not_found)
:not_found
iex(9)> Map.fetch(squares, 2)
{:ok, 4}
iex(10)> Map.fetch(squares, 4)
:error
iex(11)> Map.fetch!(squares, 2)
4
iex(12)> Map.fetch!(squares, 4)
** (KeyError) key 4 not found in: %{1 => 1, 2 => 4, 3 => 9}
(stdlib) :maps.get(4, %{1 => 1, 2 => 4, 3 => 9})
iex(13)> squares = Map.put(squares, 4, 16)
%{1 => 1, 2 => 4, 3 => 9, 4 => 16}
iex(14)> squares[4]
16
Structured data
bob = %{:name => "Bob", :age => 25, :works_at => "Initech"}
iex(2)> bob = %{name: "Bob", age: 25, works_at: "Initech"}
iex(3)> bob[:works_at]
"Initech"
iex(4)> bob[:non_existent_field]
nil
iex(5)> bob.age
25
iex(6)> bob.non_existent_field
** (KeyError) key :non_existent_field not found
iex(7)> next_years_bob = %{bob | age: 26}
%{age: 26, name: "Bob", works_at: "Initech"}
iex(8)> %{bob | age: 26, works_at: "Initrode"}
%{age: 26, name: "Bob", works_at: "Initrode"}
iex(9)> %{bob | works_in: "Initech"}
** (KeyError) key :works_in not found
2.4.7 Binaries and bitstrings
iex(1)> <<1, 2, 3>>
<<1, 2, 3>>
iex(2)> <\<256>>
<\<0>>
iex(3)> <\<257>>
<\<1>>
iex(4)> <\<512>>
<\<0>>
iex(5)> <<257::16>>
<<1, 1>>
iex(6)> <<1::4, 15::4>>
<\<31>>
iex(7)> <<1::1, 0::1, 1::1>>
<<5::size(3)>>
iex(8)> <<1, 2>> <> <<3, 4>>
<<1, 2, 3, 4>>
2.4.8 Strings
Binary strings
iex(1)> "This is a string"
"This is a string"
iex(2)> "Embedded expression: #{3 + 0.14}"
"Embedded expression: 3.14"
iex(3)> "\r \n \" \\"
iex(4)> "
This is
a multiline string
"
iex(5)> ~s(This is also a string)
"This is also a string"
iex(6)> ~s("Do... or do not. There is no try." -Master Yoda)
"\"Do... or do not. There is no try.\" -Master Yoda"
iex(7)> ~S(Not interpolated #{3 + 0.14})
"Not interpolated \#{3 + 0.14}"
iex(8)> ~S(Not escaped \n)
"Not escaped \\n"
iex(9)> """
Heredoc must end on its own line """
"""
"Heredoc must end on its own line \"\"\"\n"
iex(10)> "String" <> " " <> "concatenation"
"String concatenation"
Character lists
iex(1)> 'ABC'
'ABC'
iex(2)> [65, 66, 67]
'ABC'
iex(3)> 'Interpolation: #{3 + 0.14}'
'Interpolation: 3.14'
iex(4)> ~c(Character list sigil)
'Character list sigil'
iex(5)> ~C(Unescaped sigil #{3 + 0.14})
'Unescaped sigil \#{3 + 0.14}'
iex(6)> '''
Heredoc
'''
'Heredoc\n'
iex(7)> String.to_charlist("ABC")
'ABC'
2.4.9 第一个类函数
iex(1)> square = fn x ->
x * x
end
iex(2)> square.(5)
25
iex(3)> print_element = fn x -> IO.puts(x) end
iex(4)> Enum.each(
[1, 2, 3],
print_element
)
1
2
3
:ok
iex(5)> Enum.each(
[1, 2, 3],
fn x -> IO.puts(x) end
)
1
2
3
iex(6)> Enum.each(
[1, 2, 3],
&IO.puts/1
)
iex(7)> lambda = fn x, y, z -> x * y + z end
iex(8)> lambda = &(&1 * &2 + &3)
iex(9)> lambda.(2, 3, 4)
10
Closures
iex(1)> outside_var = 5
5
iex(2)> my_lambda = fn ->
IO.puts(outside_var)
end
iex(3)> my_lambda.()
5
iex(1)> outside_var = 5
iex(2)> lambda = fn -> IO.puts(outside_var) end
iex(3)> outside_var = 6
iex(4)> lambda.()
5
2.4.10 其他内建类型
有几种类型我还没有呈现。我们不会深入讨论它们,但为了完整起见,值得一提:
- Reference
- pid
- port identifier
2.4.11 Higher-level types
Range
iex(1)> range = 1..2
iex(2)> 2 in range
true
iex(3)> -1 in range
false
iex(4)> Enum.each(
1..3,
&IO.puts/1
)
1
2
3
Keyword lists
iex(1)> days = [{:monday, 1}, {:tuesday, 2}, {:wednesday, 3}]
iex(3)> Keyword.get(days, :monday)
1
iex(4)> Keyword.get(days, :noday)
nil
iex(5)> days[:tuesday]
2
iex(6)> IO.inspect([100, 200, 300])
[100, 200, 300]
iex(7)> IO.inspect([100, 200, 300], [width: 3])
[100,
200,
300]
iex(8)> IO.inspect([100, 200, 300], width: 3, limit: 1)
[100,
...]
def my_fun(arg1, arg2, opts \\ []) do
...
end
MapSet
iex(1)> days = MapSet.new([:monday, :tuesday, :wednesday])
#MapSet<[:monday, :tuesday, :wednesday]>
iex(2)> MapSet.member?(days, :monday)
true
iex(3)> MapSet.member?(days, :noday)
false
iex(4)> days = MapSet.put(days, :thursday)
#MapSet<[:monday, :thursday, :tuesday, :wednesday]>
iex(5)> Enum.each(days, &IO.puts/1)
monday
thursday
tuesday
wednesday
Times and dates
iex(1)> date = ~D[2018-01-31]
~D[2018-01-31]
iex(2)> date.year
2018
iex(3)> date.month
1
iex(1)> time = ~T[11:59:12.00007]
iex(2)> time.hour
11
iex(3)> time.minute
59
iex(1)> naive_datetime = ~N[2018-01-31 11:59:12.000007]
iex(2)> naive_datetime.year
2018
iex(3)> naive_datetime.hour
11
iex(4)> datetime = DateTime.from_naive!(naive_datetime, "Etc/UTC")
iex(5)> datetime.year
2018
iex(6)> datetime.hour
11
iex(7)> datetime.time_zone
"Etc/UTC"
2.4.12 IO lists
iex(1)> iolist = [[['H', 'e'], "llo,"], " worl", "d!"]
iex(2)> IO.puts(iolist)
Hello, world!
iex(3)> iolist = []
iolist = [iolist, "This"]
iolist = [iolist, " is"]
iolist = [iolist, " an"]
iolist = [iolist, " IO list."]
[[[[[], "This"], " is"], " an"], " IO list."]
iex(4)> IO.puts(iolist)
This is an IO list.
2.5 Operators
iex(1)> 1 == 1.0
true
iex(2)> 1 === 1.0
false
2.6 Macros
unless some_expression do
block_1
else
block_2
end
if some_expression do
block_2
else
block_1
end
网友评论