在函數式編程中,我們總是希望能寫出更富表達能力的函式,儘可能簡化每個 function body 需要處理的內容。Guards 讓你可以在函式的第一行,快速處理一些常用的條件判別。搭配上之前所學到的 function pattern matching,能讓程式更易於閱讀與維護。我們先看一個簡單的例子:
defmodule Math do
  def abs(i) when i < 0, do: -i
  def abs(i), do: i
end
Guards 就是在函式宣告的參數括號後方,加上 when 及一個回傳布林值的 expression。在這個例子中,我們在第一個函式區塊判斷傳入的 i  是否小於 1。而當 guards 判別回傳 false 時,就會向下繼續尋找可用的函式區塊。
由於有編譯及執行速度上的考量,guards 只能用限定的函式及操作符,包括四則運算、大小判斷如  == 、>、<,型別判斷如 is_list,is_boolean 等,更詳細的清單請參考官網的詳細列表。
特別要注意的是在 guards 裡不能呼叫自定義的一般函式。目前若需要自定義 guard,需要用進階的  defmacro ,並在該 macro 中只用到上述清單中允許的函式。而且除了檢查並回傳布林值之外,不能有其它的副作用。而新版的 elixir 中也將會提供  defguard 用來自訂義 guard,並在編譯期進行檢查。
Guards 可以用在三個地方,分別是具名函式及匿名函式宣告,及 case 表達式裡,會在相應的介紹裡說明。
若要在同個函式區塊中使用多個 guards,要用  and  或是 or。 and 就是必須符合所有的條件,而 or 則是只要任一條件符合即可。
  def is_even(n) when is_integer(n) and rem(n, 2) == 0, 
    do: true
  def is_even(_other),
    do: false
  def is_number(term)
    when is_integer(term) 
    or is_float(term),
    do: true
  def is_number(_other),
    do: false
另外依程式可讀性需求,你也可以把 or 改成 when。
def is_number(term)
  when is_integer(term)
  when is_float(term),
  do: true
def is_number(_other),
  do: false
|>不管是寫哪種程式語言,只看程式的形狀,你一定做過類似這樣的事情:
request = generate_request
response = get_response(request)
body = parse_body(response, :html)
html = render(body)
但仔細想想,當你最終想要的只有那個 html 的結果,那這個例子中的 request、response 跟 body 都是沒有用的臨時變數。想要去掉這些變數,你可以這樣寫:
html = render(parse_body(get_response(generate_request), :html))
然後你就被同事記恨了。因為這看起來更醜,得要從裡面讀到外面,而且還常常找不到開始讀的點。大概只有寫 LISP 的人才看得習慣。
Elixir 的 pipe operator |> 讓你把 expression 的結果當成下一個函式呼叫的 第一個參數,所以上面的例子可以改寫成:
html = 
  generate_request
  |> get_response
  |> parse_body(:html)
  |> render
在數學中,這個行為叫做 function composition,用 Haskell 為例,當你想要得到以下式子的結果:
y = f(g(h(x)))
你想要的,就是 f, g 跟 h 的複合函數,在 Haskell 中,你可以這樣寫:
y = f.g.h x
但雖然省掉了臨時變數跟括號, function composition 的寫法依然是由內而外的。Elixir 的 pipe operator 則是符合執行順序的:
y = x |> h |> g |> f
著名的 JavaScript FP 函式庫 Ramda 及 Lodash/fp 都提供了兩種方向的函式。Ramda 裡是 R.compose 及 R.pipe, 在 lodash/fp 裡叫做 fp.compose 及 fp.pipe 。
記得我們之前為了取得執行的結果,做了這樣的事:
y = Math.add_one(10)
IO.inspect(y)
你可以把該例子改成這樣:
Math.add_one(10)
|> IO.inspect
而 IO.inspect 不同於專用於字串列印的 IO.puts,它會將接收到的參數印出來,並將結果繼續向下傳。所以你還可以這樣做:
html = 
  generate_request
  |> IO.inspect
  |> get_response
  |> IO.inspect
  |> parse_body(:html)
#  |> IO.inspect
  |> render
  |> IO.inspect
不想印某些結果時,只要像上面把該行註解掉就好。
Pipe operator 是 Elixir 裡最著名也最重要的操作符,請務必要熟悉它的用法。
when  幫函式加上 guards ,直接對值進行檢查case 表達式中|> 會將表達式的結果,當做下一個函式呼叫的第一個參數
IO.inspect 會列印及回傳接收到的參數Happy hacking! 明天見。
文章中第一個範例的 defmodule 的尾巴是不是少了個 do 。 qq
defmodule Math
  def abs(i) when i < 0, do: -i
  def abs(i), do: i
end
                                    啊,是的。感謝修正。