Elixir 簡介

wang_yb發表於2018-04-13

概述

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 可以讓你以另外一種視角看待程式設計。