Elixir 的函數(function)跟大多數流行語言一樣,也是一等公民(first class citizens),這代表函數可以當成是變數一樣地傳遞。
匿名函數(Anonymous Functions)
正如其名,這個函數是沒有名字的。要定義一個匿名函數,會使用 fn
與 end
關鍵字。定義與呼叫的範例如下:
sum = fn (x, y) -> x + y end
IO.puts sum.(2, 3)
|
它也有簡寫的寫法:
sum = &(&1 + &2)
IO.puts sum.(2, 3)
|
其中裡面的 &1
與 &2
代表的是第一個參數與第二個參數。
模式比對(Pattern Matching)
Elixir 的模式比對除了用在變數外,也可以用在函數的 signatures 上。這功能就類似 Java 的多載(overload)有多種 signatures 一樣,但又比多載有著不同方向的彈性。舉例如下:
handler = fn {:ok} -> IO.puts "Success"
{:ok, result} -> IO.puts "Success and result: " <> result
{:error} -> IO.puts "ERROR!" end
handler.({:ok}) handler.({:ok, "some result"}) handler.({:error})
|
上面三個傳入值,依續會比對上面三種 pattern。比對是有順序的,會由上而下比對,若寫不好就有可能會有 pattern 永遠進不去,不過 Elixir 會出現 warning 提醒:
some = fn {:ok, result} -> IO.puts "Success and result: " <> result
{:ok, something} -> IO.puts "Nooooooooo!" end
some.({:ok, "some result"})
|
命名函數(Named Functions)
命名函數必須要「住」在 module 下,函數定義的方法是使用 def
巨集(另外有一個是私有函數,使用 defp
),先看簡單的定義:
defmodule Math do def context do sum(2, 3) end
def sum(x, y) do x + y end end
IO.puts Math.sum(2, 3) IO.puts Math.context()
|
不管是從外部呼叫,或是從 context() 做內部呼叫,都是可以正常執行的。
這裡可以發現匿名函數與命名函數的呼叫差異:呼叫匿名函數時,使用點(.
)和括號是絕對必要的。這樣能明確區分匿名函數與命名函數。看下面這個組合例子:
defmodule Math do def context do sum = getFn()
IO.puts sum(2, 3)
IO.puts sum.(2, 3)
IO.puts getFn().(2, 3) end
def sum(x, y), do: x + y
def getFn(), do: fn (x, y) -> x + y end end
Math.context()
|
不一定每個語言都有辨認上的問題,如 PHP 的變數需要 $
所以容易區分;而 Go 或 Javascript 可能就會比較難區別。
函數命名
Elixir 的世界裡,是使用「函數名」 + 「引數的數量」來辨別不同函數的。我們可以使用 Function.info/1
來查看函數細節;另外也可以用 &
來轉換現有的命名函數為變數。舉個例子:
iex> Function.info(&apply/1) ** (CompileError) iex:11: undefined function apply/1 iex> Function.info(&apply/2) [module: :erlang, name: :apply, arity: 2, env: [], type: :external] iex> Function.info(&apply/3) [module: :erlang, name: :apply, arity: 3, env: [], type: :external]
iex> is_function &apply/1 ** (CompileError) iex:12: undefined function apply/1 iex> is_function &apply/2 true iex> is_function &apply/2 true iex> is_function &apply/2, 1 false iex> is_function &apply/2, 2 true iex> is_function &apply/3 true
|
從這個例子可以理解 apply/1
、apply/2
與 apply/3
是三個不同的函數。其中 is_function/2
的第二個參數,是確認這個函數是否有 n 個引數,所以 apply/2
要傳 2 進去,才會回傳 true。
而這跟函數多載(overload)是不同的,在 Elixir 裡,會認為這些是不同的函數。像模式比對只會對相同數量的引數有效,因此下面這個定義是不合法的:
handler = fn (:ok, result) -> IO.puts "Success and result: " <> result
{:ok, result} -> IO.puts "Success and result: " <> result end
|
反之,我們是可以這樣定義的:
defmodule Math do def sum(), do: 0 def sum(x), do: x def sum(x, y), do: x + y end
IO.puts Math.sum() IO.puts Math.sum(10) IO.puts Math.sum(10, 20)
|