Skip to main content

Command Palette

Search for a command to run...

Elixir 的 & 運算子

Published
2 min read

在跟朋友討論 Elixir 的過程中,發現常會需要解釋 & 運算子的用法,決定寫篇完整的來科普一下。

因為特殊符號很難 google 到正確的結果,& 在官方文件中的稱呼是 capture operator。它最主要的作用,就是補獲或是生成匿名函式。

Eta conversion

在 JavaScript 中,具名函式也是一級公民,可以直接傳遞。所以遇到在 lambda 中將接收到的參數原封不動傳給具名函式,並回傳其結果的情況下,可以直接傳遞具名函式。這在數學上叫 Eta conversion。講起來很拗口,看範例就很直覺:

[-1, -2, 3].map(i => Math.abs(i))

// 等同於

[-1, -2, 3].map(Math.abs)

但在 Elixir 中,具名函式不加括號視同零參數的呼叫,因此我們需要有辦法將具名函式轉換成 lambda。這就是 & 的第一個用法。在轉換其它 module 的函式 (正式名稱叫 remote function) 時,語法是 &Module.function/arity,記得斜線後要帶上參數的個數。

Enum.map([:a, :b, :c], fn a -> Atom.to_string(a) end)

# 等同於

Enum.map([:a, :b, :c], &Atom.to_string/1)

#=> ["a", "b", "c"]

# 不能這樣寫
Enum.map([:a, :b, :c], Atom.to_string)

# 因為上一句會被解析成這樣:
Enum.map([:a, :b, :c], Atom.to_string())

當然轉換 local fucntion 或 imported function 也沒問題,不加 Module 名稱就可以。

def double(i), do: i * 2

Enum.map([1, 2, 3], &double/1)

#=> [2, 4, 6]

順帶一提,用 & 補獲/生成的函式不一定要寫在高階函式中,也可以另外指派給變數。由於被轉換成 lambda 了,所以呼叫時要用 .()

f = &Kernel.is_atom/1

f.(:atom) #=> true

匿名函式

Elixir/Erlang 裡,匿名函式的宣告比較冗長。因此遇到函式本體很短的情況下會覺得麻煩。

Enum.map([1, 2, 3], fn i -> i * 2 end)

這種情況就是 & 運算子派上用場的另一個地方,這也是最多人感到困惑的用法。

Enum.map([1, 2, 3], &(&1 * 2))
#=> [2, 4, 6]

換句話說,fn i -> i * 2 end&(&1 * 2) 是一樣的意思。

生成 List 或 Tuple

若用 []{} 代替圓括號,呼叫後的結果會分別是 ListTuple

l = &[&1, &2]
l.(1, 2)
#=> [1, 2]

t = &{&1, &2}
t.(1, 2)
#=> {1, 2}

更多參數及使用判準

&1 是匿名函式接收到的第一個參數,多個參數也是可以的,就是 &2&3 遞增下去。

fn = &(&1 + &2 + &3)

fn.(1, 2, 3) #=> 6

不過濫用 &() 語法的話,程式很容易就會變得難讀。個人的判準是內部超過 10 個字元,或是有三個以上的運算子,就寧願用 fn -> end 來宣告了。

那麼要做出 Haskell 的 identity 就簡單了: &(&1)

Enum.group_by(["a", "b", "c", "a", "b"], &(&1))

# => %{"a" => ["a", "a"], "b" => ["b", "b"], "c" => ["c"]}

Partial application

綜合上面兩個語法,有種文件上沒寫清楚,很少人提,卻相當有用的用法。送了 PR 但還沒併進去。用 &1&Module.function 作出 partially applied 的 remote function:

take_five = &Enum.take(&1, 5)
take_five.(1..100)

# => [1, 2, 3, 4, 5]

local function 也是一樣的:

first_elem = &elem(&1, 0)
first_elem.({1, 2, 3})
#=> 1

大概就是這樣了。Happy hacking!

More from this blog

聊聊 Elixir 中的 type

最近有幾位朋友分別來問 Elixir 的 type 的問題,想說中文世界好像沒有比較完整的東西,就把知道的東西整理出來。 (目前) Elixir 的 type 能做什麼? tl;dr: 最主要是文件,然後在某種程度下防止錯誤。 我覺得這應該是在研究 Elixir 的 type 時最需要知道的事情了。不像 Haskell 及 F# 這種以型別著稱的 ML 系語言,Elixir / Erlang 本質上是個動態語言,所有與型別有關的標註都會被編譯器忽略。而 Erlang 內建的型別檢查工具 dia...

Oct 18, 20222 min read

Steam 上的程式教學類遊戲

農曆年期間比較有空,玩了一些之前買的遊戲。這次特別試了幾個標榜讓不會寫程式的人學寫程式的遊戲。分享一下試玩的心得。 1. 7 Billion Humans 考慮到劇情的話我最喜歡的是 7 Billion Humans。它用拖拉語法的方式下指令,一開始還蠻好上手的,但是因為只有 goto 那樣的結構,而操作的時候又是一次對所有的 worker 下指令,所以常常要想一下執行後每個人運作的順序。但是介面有正體中文,以「想要體驗一下寫程式大概是怎麼一回事」來說還蠻適合的。 2. while Tru...

Feb 24, 20201 min read

Let's (re)start from here.

最近的時間大半都花在這上面了。 算算應該是第五次弄部落格系統。算一下扣除上古時期用現成的之外,每個系統平均各寫六篇文章,也都撐不過兩年。前幾個分別用了 Refinery CMS -> jekyll -> middleman -> jekyll。想來架系統的總時數應該超過寫文章的時間 XD 而這次用上了 Gatsby + tailwindcss,除了恢復一下 GraphQL 的手感之外,這次還挑戰了不套別人做的版型,自己把類似上一個部落格的 style 刻出來。想說來分享一下這些技術的感想: G...

Jan 11, 20201 min read

Mostly Functional

31 posts

/.(ex|jsx?|rb|hs|rs|py)/, A father, a bookworm, a pluviophile. Co-organizer of http://Elixir.tw. Learning Satir, coaching & mediation.