Elixir 是一门构建在Erlang VM 之上的函数式编程语言。Elixir 完全兼容 Erlang, 另外还提供了更标准的语法,特性。
x# 这是单行注释, 注释以井号开头# 没有多行注释# 但你可以堆叠多个注释。# elixir shell 使用命令 `iex` 进入。# 编译模块使用 `elixirc` 命令。# 如果安装正确,这些命令都会在环境变量里## ---------------------------## -- 基本类型## ---------------------------# 数字3 # 整型0x1F # 整型3.0 # 浮点类型# 原子(Atoms),以 `:`开头:hello # atom# 元组(Tuple) 在内存中的存储是连续的{1,2,3} # tuple# 使用`elem`函数访问元组(tuple)里的元素:elem({1, 2, 3}, 0) #=> 1# 列表(list)[1,2,3] # list# 可以用下面的方法访问列表的头尾元素:[head | tail] = [1,2,3]head #=> 1tail #=> [2,3]# 在elixir,就像在Erlang, `=` 表示模式匹配 (pattern matching) # 不是赋值。## 这表示会用左边的模式(pattern)匹配右侧# # 上面的例子中访问列表的头部和尾部就是这样工作的。# 当左右两边不匹配时,会返回error, 在这个# 例子中,元组大小不一样。# {a, b, c} = {1, 2} #=> ** (MatchError) no match of right hand side value: {1,2}# 还有二进制类型 (binaries)<<1,2,3>> # binary# 字符串(Strings) 和 字符列表(char lists)"hello" # string'hello' # char list# 多行字符串"""I'm a multi-linestring."""#=> "I'm a multi-line\nstring.\n"# 所有的字符串(Strings)以UTF-8编码:"héllò" #=> "héllò"# 字符串(Strings)本质就是二进制类型(binaries), 字符列表(char lists)本质是列表(lists)<<?a, ?b, ?c>> #=> "abc"[?a, ?b, ?c] #=> 'abc'# 在 elixir中,`?a`返回 `a` 的 ASCII 整型值 ?a #=> 97# 合并列表使用 `++`, 对于二进制类型则使用 `<>`[1,2,3] ++ [4,5] #=> [1,2,3,4,5]'hello ' ++ 'world' #=> 'hello world'<<1,2,3>> <> <<4,5>> #=> <<1,2,3,4,5>>"hello " <> "world" #=> "hello world"## ---------------------------## -- 操作符(Operators)## ---------------------------# 一些数学运算1 + 1 #=> 210 - 5 #=> 55 * 2 #=> 1010 / 2 #=> 5.0# 在 elixir 中,操作符 `/` 返回值总是浮点数。# 做整数除法使用 `div`div(10, 2) #=> 5# 为了得到余数使用 `rem`rem(10, 3) #=> 1# 还有 boolean 操作符: `or`, `and` and `not`.# 第一个参数必须是boolean 类型true and true #=> truefalse or true #=> true# 1 and true #=> ** (ArgumentError) argument error# Elixir 也提供了 `||`, `&&` 和 `!` 可以接受任意的类型# 除了`false` 和 `nil` 其它都会被当作true.1 || true #=> 1false && 1 #=> falsenil && 20 #=> nil!true #=> false# 比较有: `==`, `!=`, `===`, `!==`, `<=`, `>=`, `<` 和 `>`1 == 1 #=> true1 != 1 #=> false1 < 2 #=> true# `===` 和 `!==` 在比较整型和浮点类型时更为严格:1 == 1.0 #=> true1 === 1.0 #=> false# 我们也可以比较两种不同的类型:1 < :hello #=> true# 总的排序顺序定义如下:# number < atom < reference < functions < port < pid < tuple < list < bit string# 引用Joe Armstrong :“实际的顺序并不重要,# 但是,一个整体排序是否经明确界定是非常重要的。”## ---------------------------## -- 控制结构(Control Flow)## ---------------------------# `if` 表达式if false do "This will never be seen"else "This will"end# 还有 `unless`unless true do "This will never be seen"else "This will"end# 在Elixir中,很多控制结构都依赖于模式匹配# `case` 允许我们把一个值与多种模式进行比较:case {:one, :two} do {:four, :five} -> "This won't match" {:one, x} -> "This will match and assign `x` to `:two`" _ -> "This will match any value"end# 模式匹配时,如果不需要某个值,通用的做法是把值 匹配到 `_` # 例如,我们只需要要列表的头元素:[head | _] = [1,2,3]head #=> 1# 下面的方式效果一样,但可读性更好[head | _tail] = [:a, :b, :c]head #=> :a# `cond` 可以检测多种不同的分支# 使用 `cond` 代替多个`if` 表达式嵌套cond do 1 + 1 == 3 -> "I will never be seen" 2 * 5 == 12 -> "Me neither" 1 + 2 == 3 -> "But I will"end# 经常可以看到最后一个条件等于'true',这将总是匹配。cond do 1 + 1 == 3 -> "I will never be seen" 2 * 5 == 12 -> "Me neither" true -> "But I will (this is essentially an else)"end# `try/catch` 用于捕获被抛出的值, 它也支持 `after` 子句,# 无论是否值被捕获,after 子句都会被调用# `try/catch` try do throw(:hello)catch message -> "Got #{message}."after IO.puts("I'm the after clause.")end#=> I'm the after clause# "Got :hello"## ---------------------------## -- 模块和函数(Modules and Functions)## ---------------------------# 匿名函数 (注意点)square = fn(x) -> x * x endsquare.(5) #=> 25# 也支持接收多个子句和卫士(guards).# Guards 可以进行模式匹配# Guards 使用 `when` 关键字指明:f = fn x, y when x > 0 -> x + y x, y -> x * yendf.(1, 3) #=> 4f.(-1, 3) #=> -3# Elixir 提供了很多内建函数# 在默认作用域都是可用的is_number(10) #=> trueis_list("hello") #=> falseelem({1,2,3}, 0) #=> 1# 你可以在一个模块里定义多个函数,定义函数使用 `def`defmodule Math do def sum(a, b) do a + b end def square(x) do x * x endendMath.sum(1, 2) #=> 3Math.square(3) #=> 9# 保存到 `math.ex`,使用 `elixirc` 编译你的 Math 模块# 在终端里: elixirc math.ex# 在模块中可以使用`def`定义函数,使用 `defp` 定义私有函数# 使用`def` 定义的函数可以被其它模块调用# 私有函数只能在本模块内调用defmodule PrivateMath do def sum(a, b) do do_sum(a, b) end defp do_sum(a, b) do a + b endendPrivateMath.sum(1, 2) #=> 3# PrivateMath.do_sum(1, 2) #=> ** (UndefinedFunctionError)# 函数定义同样支持 guards 和 多重子句:defmodule Geometry do def area({:rectangle, w, h}) do w * h end def area({:circle, r}) when is_number(r) do 3.14 * r * r endendGeometry.area({:rectangle, 2, 3}) #=> 6Geometry.area({:circle, 3}) #=> 28.25999999999999801048# Geometry.area({:circle, "not_a_number"})#=> ** (FunctionClauseError) no function clause matching in Geometry.area/1#由于不变性,递归是Elixir的重要组成部分defmodule Recursion do def sum_list([head | tail], acc) do sum_list(tail, acc + head) end def sum_list([], acc) do acc endendRecursion.sum_list([1,2,3], 0) #=> 6# Elixir 模块支持属性,模块内建了一些属性,你也可以自定义属性defmodule MyMod do @moduledoc """ 内置的属性,模块文档 """ @my_data 100 # 自定义属性 IO.inspect(@my_data) #=> 100end## ---------------------------## -- 记录和异常(Records and Exceptions)## ---------------------------# 记录就是把特定值关联到某个名字的结构体defrecord Person, name: nil, age: 0, height: 0joe_info = Person.new(name: "Joe", age: 30, height: 180)#=> Person[name: "Joe", age: 30, height: 180]# 访问name的值joe_info.name #=> "Joe"# 更新age的值joe_info = joe_info.age(31) #=> Person[name: "Joe", age: 31, height: 180]# 使用 `try` `rescue` 进行异常处理try do raise "some error"rescue RuntimeError -> "rescued a runtime error" _error -> "this will rescue any error"end# 所有的异常都有一个messagetry do raise "some error"rescue x in [RuntimeError] -> x.messageend## ---------------------------## -- 并发(Concurrency)## ---------------------------# Elixir 依赖于 actor并发模型。在Elixir编写并发程序的三要素:# 创建进程,发送消息,接收消息# 启动一个新的进程使用`spawn`函数,接收一个函数作为参数f = fn -> 2 * 2 end #=> #Function<erl_eval.20.80484245>spawn(f) #=> #PID<0.40.0># `spawn` 函数返回一个pid(进程标识符),你可以使用pid向进程发送消息。# 使用 `<-` 操作符发送消息。# 我们需要在进程内接收消息,要用到 `receive` 机制。defmodule Geometry do def area_loop do receive do {:rectangle, w, h} -> IO.puts("Area = #{w * h}") area_loop() {:circle, r} -> IO.puts("Area = #{3.14 * r * r}") area_loop() end endend# 编译这个模块,在shell中创建一个进程,并执行 `area_looop` 函数。pid = spawn(fn -> Geometry.area_loop() end) #=> #PID<0.40.0># 发送一个消息给 `pid`, 会在receive语句进行模式匹配pid <- {:rectangle, 2, 3}#=> Area = 6# {:rectangle,2,3}pid <- {:circle, 2}#=> Area = 12.56000000000000049738# {:circle,2}# shell也是一个进程(process), 你可以使用`self`获取当前 pid self() #=> #PID<0.27.0>