概述
Elixir 是一種基於 Erlang 虛擬機的函數式,面向並行的通用語言, 它是一門通用語言,所以不僅可以用在擅長的高可用,高並發場景下,也可以用在 web 開發等場景下。 Erlang 誕生於 1986 年,愛立信。
有了 Erlang,為什么還要 Elixir? Erlang 畢竟誕生的早,雖然有很多優秀的特性,但是語法非常晦澀難懂,甚至沒有支持 String Elixir 只是 Erlang 很簡單的封裝,不僅保留了 Erlang 所有的優秀特性,還提供了類似 Ruby 那樣高效的語法。
和 Erlang 相比,Elixir 的語法有 2 個明顯的優勢
- macro: 可以簡化很多的代碼
- pipeline: |> 可以省卻很多臨時變量的定義
環境搭建
官方安裝文檔:https://elixir-lang.org/install.html 建議在 Unix-like 的系統下通過編譯源碼的方式安裝,能夠及時體驗最新的特性
安裝 Elixir 之前要先安裝 Erlang
語言基礎
在 Elixir 中,任何東西都可以理解為 *表達式+返回值*。 Elixir 中,任何數據不可改變,變量的賦值本質是把變量指向了其他的內存地址,不改變變量原來內存地址中的內容, 所以,等於(=)在 Elixir 中其實是 綁定(binding) 的意思,把右邊的值綁定到左邊的變量上,並返回右邊的值
類型
Elixir 中的類型主要有以下幾類:
Number
Elixir 中整數和小數統稱為數值類型,整數的除法和求余用 div 和 rem 宏來實現
Atom
原子存在原子表中,不會被 GC 自動回收 true/false 在 Elixir 中是原子 :true/:false nil 在 Elixir 中是原子 :nil
Tuple
元素個數是固定的,適用於小的集合 通過 put_elem 修改 tuple 之后,其實返回的是一個新的 tuple,原來的 tuple 並沒有被修改
List
list 可以表示為 [head|tail] 在 list 的末尾追加元素會導致 copy 整個 list,所以一般都在頭部添加元素
Map
如果 key 是原子 ex. %{a: "xx", b: "yy"}; 否則 %{1 => "xx", "a string key" => "yy"}
Binary and Bitstring
長度是 8 的倍數的 Bitstring 也是 Binary
String
Elixir 中的 string 本質就是 Binary
Function
function 在 Elixir 中是一等公民,所以它也可以存放在變量中
Reference
BEAM 實例的引用
pid
Erlang process 的唯一標識
Keywork List
一種 list,每個元素都是一個包含 2 個元素的 tuple
IO List
一種深度自包含的結構,可以用來高效的在處理 IO 和網絡數據 ex. 下面這個例子,文件寫入時會分成 3 次,因為 a,b,c 的內存地址是不連續的 如果把 a,b,c 拼成一個字符串再寫入文件的話,新的字符串會再次分配一次 a,b,c 所占用的內存量
{:ok, file} = :file.open("/tmp/tmp.txt", [:write, :raw])
a = "aaa"
b = "bbb"
c = "ccc"
output = [a, b, c]
:file.write(output)
用 io_list 可以避免上面的問題
{:ok, file} = :file.open("/tmp/tmp.txt", [:write, :raw])
a = "aaa"
b = "bbb"
c = "ccc"
output = [a, [b, [c]]]
:file.write(output)
IO.puts 輸出時會拍平 io_list
控制流
一般語言中的流程控制就是判斷(if, case…),循環(for, while…) Elixir 中雖然也有 if,case(通過 macro 來實現),但是盡量不要使用
Elixir 中通過模式匹配來實現判斷,通過遞歸來實現循環
模式匹配的例子
通過不同的函數,實現變量的類型判斷
defmodule ElixirIntro do
def judge(x) when is_integer(x) do
IO.puts("#{x} is integer")
end
def judge(x) when is_bitstring(x) do
IO.puts("#{x} is string")
end
def judge(x) do
IO.puts("#{x} is not integer and string")
end
end
遞歸的示例
遞歸是 Elixir 中常用的技巧,通過遞歸可以寫出簡單易懂的代碼 下面示例是累加求和的遞歸寫法
defmodule ElixirIntro do
def sum(n) when n <= 1 do
n
end
def sum(n) do
n + sum(n - 1)
end
end
上面的遞歸寫法雖然能完成功能,但是運行過程中消耗的內存很比較大,這種寫法就是遞歸被人詬病的地方。
在 Elixir 中,我們應該使用尾遞歸的方式來完成循環,因為尾遞歸會被優化,只占用固定數量的內存,上面的示例如下:
defmodule ElixirIntro do
def sum(total, n) when n <= 1 do
total + n
end
def sum(total, n) do
sum(total + n, n - 1)
end
end
所謂尾遞歸,就是函數在最后只調用了自己
代碼組織(模塊和函數)
Elixir 的代碼用 mix 來管理,通過 mix 創建,管理,發布工程。 代碼主要是 module 和 function
$ mix new hello
$ cd hello
$ iex -S mix
Elixir 工程的代碼都在 lib 文件夾下。
錯誤處理
錯誤處理是 Elixir 中的一級概念。
一般的錯誤處理思路都是盡可能的捕獲錯誤(可預期和不可預期的):
- 可預期的錯誤直接處理
- 不可預期的錯誤捕獲不到可能系統崩潰,捕獲后一般也是直接跳過
Elixir 的核心能力之一是應對高並發,所以它的錯誤處理思路也不一樣。 Elixir 錯誤處理的目的 不是降低錯誤的數量 ,而是 降低錯誤帶來的影響 並且能夠從錯誤中 自動恢復 。 所以,Elixir 的處理方式:
- 可預期的錯誤,和傳統方式一樣,盡可能處理
- 不可預期的錯誤,直接崩潰重啟。會導致崩潰的地方盡快崩潰
Elixir 的觀點:
- 有些錯誤發生后,要想解決很困難,在發生錯誤后接着處理可能會導致更嚴重的錯誤
- 很多運行時的錯誤,極難重現,大部分都能通過重啟來解決
總結
Elixir 雖然在它的領域有先天的優勢,但是肯定也有不足之處:
- speed: 性能不是 Erlang 平台的優勢,畢竟有一層 BEAM 虛擬機, 高並發 != 高性能
- ecosystem: Erlang/Elixir 的生態遠沒有其他語言成熟(比如 java, javascript, ruby 等)
Elixir 不是萬能的,但是學習 Elixir 可以打開你的視野,特別是對於一直使用面向對象語言的同學, 學習 Elixir 可以讓你以另外一種視角看待程序設計。