在IT世界裏,沒有銀彈,但卻有神奇的仙丹(Elixir)。我不知道是什麼靈感刺激這門語言的創造者José Valim想到了這麼酷的命名,但這枚仙丹確實經由多種神奇的靈藥煉製而成,這些靈藥包括Erlang、Ruby、Clojure、Haskell。javascript
品嚐這枚仙丹確實使人飄飄欲仙,至少,我在淺嘗Elixir時,這種奇妙的感受一直縈繞在我心間,怦然心動於是不捨離去。或許如Erlang之父Joe Armstrong所說,是「一種先行於邏輯的心裏感性的感受」;又或者如Dave Thomas形容的,那是讓人「墜入愛河」的感受。java
大愛Elixir。git
我之因此愛上Elixir,大約仍是由於Ruby的緣故。我並不是Ruby的狂熱追隨者,甚至沒有從事太多Ruby相關的項目,但我至今在編寫腳本時,Ruby依舊是個人首選。在動態語言中,我甚喜好Ruby相對簡潔的語法。當我看到Elixir時,那種似曾相識的感受讓我心動。程序員
雖說Elixir的煉製來自各位前輩留下的丹方靈藥,然而從成丹之日起,Elixir就是Elixir,她已經具備了完整的語言性格。就我看來,Elixir真正稱得上是「性感」。固然,這一大半要歸功於Erlang美麗的英倫風情(Erlang之父Joe Armstrong是英國人),就Erlang的高顏值打底,只需再加上幾點嫵媚,幾分妖嬈,風采就變得性感撩人了。github
Elixir對併發與分佈式的支持,就是正宗的英倫風情,這是從Erlang延續下來的最強悍基因。Elixir創建在Erlang虛擬機(BEAM)之上,使用Erlang的進程,如原生進程那樣在全部的處理器中運行,然而開銷卻很是小。與Erlang同樣,Elixir能夠經過spawn輕鬆地建立進程:算法
spawn fn -> 1 + 2 end複製代碼
Elixir或者說Erlang的進程依靠消息傳遞完成通訊。進程接收到的消息其實是獲取的一份消息副本,這就使得接收方可以與發送方解耦,接收方對消息的任何操做不會影響接收方。數據庫
send self, {:hello, "world"}
receive do
{:hello, msg} -> msg
{:world, msg} -> "won't match"
end複製代碼
Elixir的核心繼承自Erlang,天然就繼承了對OTP(Open Telecom Platform)的支持。OTP是一個很大的課題,包括進程連接、監控以及分佈式支持(我正在學習《Erlang/OTP併發編程實戰》,但願從Erlang根源上理解OTP)。Elixir對OTP的支持包括Agent、Task、GenServer以及Supervisor與Application。其中,Agent與Task是Elixir對OTP特性的抽象,而GenServer則更加通用。編程
在Elixir建立OTP服務器很是簡單,只須要use GenServer便可。它主要的方法爲handle_call(request, from, state)與handle_cast(request, state)。若是客戶端發送的請求須要響應時,則消息形式爲call,若是爲單向調用,則形式爲cast。服務器
考慮進程的健壯性問題,在編寫OTP應用時,可能還須要對進程進行監督。基於Actor模型,父進程將負責監督由其建立的全部子進程,下面的代碼是Elixir官方提供的Supervisor代碼:數據結構
defmodule KV.Supervisor do
use Supervisor
def start_link do
Supervisor.start_link(__MODULE__, :ok)
end
def init(:ok) do
children = [
worker(KV.Registry, [KV.Registry]),
supervisor(KV.Bucket.Supervisor, [])
]
supervise(children, strategy: :rest_for_one)
end
end複製代碼
KV.Supervisor爲監督進程,其子進程分別爲KV.Registry與KV.Bucket.Supervisor,監督策略爲rest_for_one。
至於分佈式支持,在Elixir實際上是水到渠成的事情,由於它的核心是進程間通訊,而進程所在的節點位置,對於用戶而言是透明的。
模式匹配是Elixir最妖嬈的部分,雖然不少函數式語言都有模式匹配,但Elixir卻把模式匹配融入到其血肉之中(實際上是延續了Erlang的模式匹配特點)。即便是一個賦值語句,也是模式匹配的一部分。在Elixir中,=符號其實被稱之爲匹配運算符(match operator)。因此你能夠寫出違反程序員常規的1 = x:
iex> 1 = x
1
iex> 2 = x
** (MatchError) no match of right hand side value: 1複製代碼
模式匹配在Elixir中被普遍地運用到解構(destructuring )複雜的數據結構,例如Tuple、List等。固然case進行的模式匹配更是它最多見的使用場景。
函數與模式匹配的結合纔是體現妖嬈性的關鍵點,若是再結合guard clause,那就真正讓人銷魂了。
大多數語言的函數定義是支持函數重載的,這取決於參數的類型、個數與順序。在動態語言中沒有類型,則與個數與順序相關。這些參數在定義時皆爲形參(部分語言支持默認參數值,Elixir也支持,甚至能夠將表達式做爲默認參數),在調用時才傳入實參。
可是,Elixir則否則,由於Elixir沒有賦值的概念,所以在傳遞參數時,並不是賦值的語義,而是匹配的語義。於是出現以下的函數定義,你不要感到詫異哦:
defmodule Factorial do
def of(0), do: 1
def of(n), do: n * of(n-1)
end複製代碼
在Elixir語義中,這兩個定義實則是同一個函數,當調用of函數時,傳入的參數會與第一個定義進行匹配,若是匹配不成功,則匹配第二個定義。利用這種模式匹配,既能夠規避實現上的if分支,又能夠更好地體現遞歸的語義。
Meyer很是強調軟件開發中對「契約」的遵循,在他設計的語言Eiffel中,前置條件與後置條件做爲了語法糖中的一等公民被支持。Erlang的guard Cluase與Eiffel的前置條件很是類似,Elixir也保留了這一語法特性。例如在前面的階乘算法中,咱們能夠經過guard clause避免傳入錯誤的負數:
defmodule Factorial do
def of(0), do: 1
def of(n) when n > 0, do: n * of(n-1)
end複製代碼
讓Elixir展示其嫵媚一面的,是超級性感的管道運算符。她讓整段代碼瞬間變得可愛起來。有了她,咱們就不用再陷入可怕的函數嵌套地獄中了。Dave Long在博文Playing with Elixir Pipes中給出了一個很有對照意義的例子。代碼功能是從conn取得Request的header,並判斷它是否有效。若是有效就返回conn,不然終止,並返回Not Authorized。
若是沒有管道運算符,就得承受嵌套函數調用的驚悚感:
signature = List.first(get_req_header(conn, "x-twilio-signature"))
is_valid = Validator.validate(url_from_conn(conn), conn.params, signature)
if is_valid do
conn
else
halt(send_resp(conn, 401, "Not authorized"))
end複製代碼
這樣的代碼徹底違反人類直覺,由於你得從函數最裏邊閱讀,而後再層層往外逃逸。是否有一種被牢牢捆綁了的感受呢?固然,在不少語言中咱們都無奈地接受了這一點,已經被虐得習覺得常了。嘗試一下管道運算符,會怎麼樣?
signature = conn
|> get_req_header("x-twilio-signature")
|> List.first
if conn
|> url_from_conn
|> Validator.validate(conn.params, signature)
do
conn
else
conn |> send_resp(401, "Not authorized") |> halt
end複製代碼
當你把管道運算符|>當作是goto的話,咱們就能直觀地體會到conn在各個函數中流動的現象了。很是可愛,不是嗎?
Elixir是純正的函數式語言,本質上講,Elixir中的一切皆爲函數,因此if表達式其實也是函數。這就意味着validate後的布爾結果能夠經過|>直接傳遞給if:
signature = conn
|> get_req_header("x-twilio-signature")
|> List.first
conn
|> url_from_conn
|> Validator.validate(conn.params, signature)
|> if(do: conn, else: conn |> send_resp(401, "Not authorized") |> halt)複製代碼
這纔是真正Elixir Style的編程範兒,夠嫵媚吧!
Joe Armstrong認爲管道運算符來自Prolog語言的隱性基因DCG,相似Haskell中的monad。Prolog的兒子erlang沒有體現這一點,孫子輩又隔代遺傳上了。
Elixir的創造者José Valim乃Rails的核心參與者,因此他把Rails社區(包括Ruby社區)中一套讓人目眩的工程實踐照般過來了。
經過mix能夠直接幫助咱們建立項目的腳手架(用過rails的童鞋感到親切了嗎?):
mix new myproject複製代碼
執行這條命令,mix就會幫咱們建立項目的基本結構和相應文件:
經過Hex來管理包(記得GEM嗎?)。在hex.pm中幾乎能夠找到全部你想要的elixir包;固然你還能夠享受Erlang的福利,直接重用erlang包。
添加依賴也很是方便,只須要在項目的mix.exs文件中添加依賴便可。例如添加HTTPoison和JSX包的依賴:
defp deps do
[
{:httpoison, "~> 0.11.0"},
{:jsx, "~> 2.8"}
]
end複製代碼
最棒的是,Elixir還支持直接對github repository的依賴。
對開發環境、測試環境、生產環境的配置支持。在config目錄下的config.exs文件中能夠添加必要的配置項,還能夠經過以下語句import不一樣環境的配置:
import_config "#{Mix.env}.exs"複製代碼
還有不能忘記的單元測試,這但是敏捷社區的隨身法寶啊;Elixir經過內嵌的ExUnit很好地支持了單元測試的編寫:
defmodule MyprojectTest do
use ExUnit.Case
doctest Myproject
test "sort ascending orders the correct way" do
result = sort_into_ascending_order(fake_created_at_list(["c", "a", "b"]))
issues = for issue <- result, do: issue["created_at"]
assert issues == ~w{a b c}
end
end複製代碼
如此簡單。要運行全部測試,只需運行mix test便可。
Elixir還有不少酷炫的玩意兒,例如Protocol、Behavior,固然還有最棒的(固然也多是最使人費解的)宏(Macro)。Elixir對DSL的支持也很是友好,這來自它繼承的部分Ruby血統。例如,讓咱們看看ECTO(一個基於Elixir開發的支持數據庫訪問的框架)的一段客戶代碼:
defmodule Sample.App do
import Ecto.Query
alias Sample.Weather
alias Sample.Repo
def keyword_query do
query = from w in Weather,
where: w.prcp > 0 or is_nil(w.prcp),
select: w
Repo.all(query)
end
def pipe_query do
Weather
|> where(city: "Kraków")
|> order_by(:temp_lo)
|> limit(10)
|> Repo.all
end
end複製代碼
由於沒有大括號、括號以及分號的干擾,代碼能夠變得更接近領域邏輯,再加上性感的管道運算符,可讀性直接爆表,帥呆了!