從 Go 開發者的角度看 Elixir 的設計思想
從 Go 開發者的角度看 Elixir 的設計思想
免責聲明: 這篇文章不是帶你入門的,我只是把玩了一下這個編程語言,也不是什么專家,就把我寫的當做一道開胃菜吧。我只是把我幾個小時的調研結果匯總一下,以便能夠幫助大家花幾分鐘讀完之后再看 Elixir 是否吸引到了你。
Elixir 是什么
Elixir 是運行在 Erlang 虛擬機 BEAM 上的一門新興的編程語言。它完全兼容 Erlang,并且擁有共同的組件,但是它提供了類似 Ruby 的語法以及很多的語法糖。由 Rails 核心貢獻者 José Valim 建立,它吸引了很多 Ruby 和 Erlang 的開發人員,并且試圖結合 Erlang 的強大以及 Ruby 的編程樂趣。
彈性的不可變性
Elixir 試圖成為一個嚴格意義上的面向函數的編程語言,但是有一個例外,就是數據的不可更改,一旦你定義了一個變量,就不可更改。這樣做有很大的好處,但是對于像我這樣有思維定式的人就需要適應一下。很多情況下你希望將變量用于遞歸以及函數調用中,所以 Elixir 提供了一種彈性的不可變性,就是你可以重用變量名。
Interactive Elixir (1.1.1) ... iex(1)> a = 1 1 iex(2)> a = "what?" "what?"
這看起來好像改變了變量,而實際上并沒有,它只是 a1=1 和 a2="what?" 的一種語法糖。傳給第一個變量 a 的數值仍然是 1,你可以認為 Elixir 是動態類型的,并且支持 REPL,而且這也是可以編譯通過的。
模式匹配
Elixir 像 Erlang 等很多其它的函數式編程語言一樣擁有模式匹配,它是一個殺手級特性,會改變你寫代碼方式。在 Elixir 中,= 操作符實際上是對它的兩端進行匹配。
Interactive Elixir (1.1.1) ... iex(1)> a = 1 1 iex(2)> b = 8 8 iex(3)> [a, 8, c] = [1, b, "I am a teapot"] [1, 8, "I am a teapot"] iex(4)> c "I am a teapot" iex(5)> [a, 7, c] = [1, b, "I am a teapot"] # try to match b = 7 ** (MatchError) no match of right hand side value: [1, 8, "I am a teapot"] iex(6)> [a, 8, c] = [2, b, "I am a teapot"] # rebind a = 2 [2, 8, "I am a teapot"]
這段程序試圖逐個進行匹配,直到某一個不對,在匹配的過程中也會綁定變量。模式匹配減少了 if/else 語句的濫用,可以類比為 switch 語句的逐個匹配,但是它又更進一步,你還可以在定義函數的過程中,匹配不同參數的輸入。
is_three = fn
{3} -> IO.puts "yes, this is the number 3"
{num} -> IO.puts "no, this is the number #{num}"
{num, more} -> IO.puts "no, those are 2 numbers, #{num} and #{more}"
end
iex> is_three.({3})
yes, this is the number 3
:ok
iex> is_three.({6})
no, this is the number 6
:ok
iex> is_three.({3, 6})
no, those are 2 numbers, 3 and 6
:ok
原子
它類似于 Go 中用 iota 逐個聲明的常量,也是在其它編程語言中叫符號的。它用來標記這是個什么,也可以在模式匹配中進行匹配。
iex> :imanatom
:imanatom
iex> {:imanatom, a} = {:imanatom, 10}
{:imanatom, 10}
iex> {:imanatom, a} = {:anotheratom, 10}
** (MatchError) no match of right hand side value: {:anotheratom, 10}
iex> IO.puts "hello world"
hello world
:ok
很多函數會返回 :ok 原子,用來代表是成功了或者帶有信息的 :error,這樣你可以結果來進行不同的操作。
Processes 與 Goroutines
Elixir/Erlang 中的 Processes 與 Go 的 Goroutines 是類似的,都是輕量級的線程,而且運行不依賴于系統線程。Elixir 實現了 Erlang 的 actor 模型,它的輕量級線程是可以被直接尋址的主實體,當建立一個 process 的時候,你可以獲取到相應的 PID,你可以用 PID 向 process 發送消息,process 能夠模式匹配不同的消息,從而判斷出這是什么消息以及要做什么。
Elixir actor 模型下的 hello world:
ex(1)> parent = self()
#PID<0.57.0>
iex(2)> spawn_link(fn -> send parent, {:msg, "hello world"} end)
#PID<0.60.0>
iex(3)> receive do {:msg, contents} -> IO.puts contents end
hello world
:ok
通信的通道對于 process 是通明的,實際上,所有的虛擬機都可以被連接到一個網狀網絡中,你甚至可以向當前網絡環境中其它電腦的 process 發送消息。多個 process 可以捆綁成一個 process 組,你可以向 process 組發送消息,從而讓其分配負載。由于網絡對于 process 是透明的,那么是否用微服務也就沒有那么多爭論了,你可以很容易的改變架構。
actor 模型與 Go 的 CSP 模型不同的地方在于,process 在通信的過程中是可以被直接尋址的,而 goroutine 是匿名的,無法被直接尋址。
Supervisors
Supervisors 也是 process,它可以監控你的 process,并且可以在 process 由于某種原因崩潰的時候讓其重啟。它也可以控制你的部分程序,讓其開始或停止。假設我們想監控有一個計數程序,一個類似的用 Go 實現的 supervisor 程序如下:
package mainimport (
"fmt"
"time")func main() {
// Instead of: go count(3)
go supervisor(count, 3)
select {}}func count(to int) {
for i := 0; i <= to; i++ {
fmt.Printf("i=%d\n", i)
time.Sleep(time.Second)
}
panic("pretend that something broke")}func supervisor(fn func(int), args int) {
for {
func() {
defer func() {
if err := recover(); err != nil {
fmt.Println("panic recovered")
}
}()
fn(args)
}()
fmt.Println("restarting process")
time.Sleep(time.Second)
}}
這過于簡單了,Elixir/Erlang 的 supervisor 是內建的,而且復雜的多,允許多種重啟策略等。他們通常被組合為“supervisor 樹結構”,所以你的應用可以彈性的保持各種層級。
結構體與協議
Elixir 的結構體與 Go 的類似,雖然底層都是 map 的語法糖。但是 Elixir 保持函數編程精髓的同時,給其加入了一個面向對象的好處,你可以像匹配 map 的值一樣模式匹配結構體內的屬性。
Elixir 的協議如 Go 的 interface 類似,并且實現了結構體的方法。像 Go 一樣,好像也遵循了基于繼承上架構的原則。
管線|>操作符
我,去!雖然遵循了 unix 管線的設計精髓,但是 Elixir 直接將其加入了語言特性,并且在可讀性上做了很多的改動。所有 |> 操作符左側的數值都是右側函數的第一個參數。
像這種情況:
toString(multiplyBy(fetchANumber(), 2))
可以寫成:
fetchANumber |> multiplyBy(2) |> toString
將操作順序改成了自然的方式。這同樣也是語法糖,更確切的說是你也可以自己創建的宏。
宏
宏也是語法糖,宏系統不僅是簡單的模板,Elixir 在內部表現為 lisp 式的三元素元組{函數,元數據,參數},你可以在編譯時訪問你的抽象語法樹。
iex> quote do: sum(1, 2, 3)
{:sum, [], [1, 2, 3]}
iex> quote do: 2 + 3
{:+, [context: Elixir, import: Kernel], [2, 3]}
iex> quote do: IO.puts "hello"
{{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]}, [], ["hello"]}
這讓 Elixir 非常適合元編程和成為很棒的 DSL。你可以任意在編程語言上擴展你認為缺失的東西,Elixir 并不鼓勵濫用宏,只有當你遇到一系列新的問題時再用,抱著“我這樣試試...”來驅動你的編程(WIICJDD)的就有點偏離軌道了。
標準庫與 OTP
Erlang 的標準庫內容居多,采用 PHP 的風格,而 Elixir 則不同,其更為整潔。但是最牛的是它的開放電信平臺(OTP),這個庫就是針對電信行業的分布式系統設計的。
OTP 是由設計精良的各種模塊組成的用于設計彈性分布式系統的庫,這也讓 Erlang 在某種程度上顯得十分特別。在庫里面你可以找到前文提及的 supervisor,發布管理,監控,一般的服務器實現,分布式的鍵值存儲甚至還有分布式的關系型數據庫。
Phoenix
Phoenix 由另一個 Ruby 使用者 Chris McCord 創建,它是一個高效的 web 框架,目的是成為 Elixir 的 Rails。它充分利用了 Plug 作為其核心的思想,Plug 定義了中間件的實現標準,并且使用 Ecto 作為數據庫 ORM 層。它試圖借鑒 Rails 專注與生產的特點,但是又不是 Rails 的克隆,而是充分利用了 Elixir/OTP 的特性。它不局限于 web 開發,還內建了一個可以在傳輸層加入插件的一個 socket 庫 Phoenix Channels,并且已經有 JavaScript, Swift, ObjC, C# 和 Java的實現了。
BEAM
前面提到了,BEAM 是 Elixir運行的虛擬機。不要被虛擬機給嚇到了,它 與JVM 很不一樣。它占用極少的內存,沒有顯著的內存回收停頓,性能很好。它的出色之處在于它的低延遲,計算速度介于 JAVA/Go 與 Python/Ruby 之間,并且可以在樹莓派上歡樂的玩耍。不要愁部署,OTP 提供了包含所有依賴庫的“發布助手”,像 Go 的二進制文件一樣,你只需 scp 到你的服務器即可。
最后的一些看法
私以為 Elixir 是很有趣的,對于像我這樣在 JavaScript 上面找不到太多函數編程思想的人,學習它的范式會感到很自然。模式匹配和管線操作也讓代碼的書寫和閱讀更加明了。同時,在學習構建彈性分布式系統這門課程的多年之后,也能夠有機會學習一下OTP庫。
我看到在為了性能提升的道路上,很多人從 Ruby 轉向了 Go,也有人轉向了 Elixir。一直有人說,我沒有看到它應用在某個大型應用上,最起碼在禁止使用宏定義的版本上。我曾寫過關于 Go,說過不讓用戶表達自己的好處。Elixir 和 WIICJDD 的思想則是另外一種景象,通過宏系統,你可以盡情的釋放自己。
我想把 José Valim 比喻成 Willy Wonka,他將 Erlang 轉換成了很多語法糖,給大家帶來了胃口,我把宏比喻成巧克力工廠,我也在考慮有多少開發者會不顧提醒而跳入巧克力河中。我不說更多了,你可以看我的選擇,有可能我是錯的,畢竟這門語言還太年輕。
想要更多的了解 Elixir 的設計目標,需要看看它背后的人們,以及他們曾經遇到的問題。據我所知,Valim 和 McCord 都不是 Erlang 的開發者,也不想做一個更好的 Erlang,他們都是在 web 開發機構用 Ruby 寫代碼的[see notes],他們都是想用 Erlang 的分布式的優點來彌補 Ruby可 擴展性的短板[see notes]。他們帶領了一些 Erlang 開發者。這門語言立意深遠,按照這個思路發展才是最重要的。
如果你被吸引了,想學學它,不僅有官方的入門指導,還推薦你看看 Chris McCord 的 3 個小時的研討班:都來搭 Elixir 的列車!和相關代碼。
記錄-1: 十月 17, 2015
我得知,José Valim 的雇主 Plataformatec 公司正在支持 Elixir 的開發,不僅在 web 開發,還在他們生意中的后端部分也有涉及,比如 SOA,ETL 等地方。
記錄-2: 十月 17, 2015
我提及的作者想要把 Elixir 打造成一個更好的 Ruby 只是我個人的想法,這是 Valim 本人的說法:
我不會將Elixir定義為更好的 Ruby。在 Elixir 之前,我本人的主要語言確實是 Ruby,但是我創建 Elixir 的部分工作/研究的本意是想讓其獲取更多經驗,從而豐富其生態系統。所以我對 Elixir 并沒有偏見,不是更好的 Ruby,也不是更好的 Erlang,它就是它自己。
發表于十月 16, 2015
本文地址:http://www.oschina.net/translate/elixir-concepts-for-golang-developers-
原文地址:https://texlution.com/post/elixir-concepts-for-golang-developers/