在使用 Pattern matching (模式比對) 的時候,都會想起給小朋友玩的形狀比對積木
=
)Elixir 在設定變數的時候就是 Pattern matching 了
name = "Jack"
在這邊 "Jack" 這個積木被放到沒有任何限制的 name 變數
這個行為在取出複雜結構的時候特別好用,例如 Map
%{name: "Jack", age: age_of_jack} = %{name: "Jack", age: 34}
age_of_jack
#=> 34
我們可以試著想像左邊這個 Map 挖了一個洞是 age_of_jack
被我們拿來比對右邊,符合的項目就是 34
如果 name 的值不一樣呢?
%{name: "Finn", age: age_of_jack} = %{name: "Jack", age: 34}
** (MatchError) no match of right hand side value: %{name: "Jack", age: 34}
這個時候就會跳出 MatchError 告訴你比對失敗
那這樣呢?
1 = 1
在其他的語言, =
是作為賦予變數右邊的值,所以通常這樣寫都會錯誤,
但在 Elixir 裡是 pattern matching,我們拿右邊的值來跟左邊比較,如果有變數的洞再填符合的項目進去,
在 1 = 1
這個情況,就是 pattern matching 成功,而左邊沒有任何空位需要補,所以忽略。
但是如果寫 1 = 3
這樣子就會 MatchError 了。
在 match Tuple 的時候,我們需要確定兩邊的長度一致
很多函式會回傳 {:ok, 結果}
這種結構的 tuple,
我們就可以用 pattern matching 把結果接起來
{:ok, result} = {:ok, "Jack"}
result
#=> "Jack"
{:ok, users} = {:ok, [%{name: "Jack"}, %{name: "Finn"}]}
users
#=> [%{name: "Jack"}, %{name: "Finn"}]
與 tuple 一樣,list 需要一樣的長度才會成功
[1, x] = [1, 2]
x
#=> 2
在 Elixir 的 list , 因為他是 Linked list ,從前面取值比較有效率 (原理之後有篇幅再說明)
所以 Elixir 提供一個給 List 用的特殊匹配法,就算不知道長度也可以使用
[head | tail] = [1, 2, 3, 4, 5]
head
#=> 1
tail
#=> [2, 3, 4, 5]
在左邊使用 [第一項 | 剩下的]
這個結構來取出 list 的第一項,與剩餘的 list
這個結構在寫遞迴的時候特別好用(之後說明)
因為 Map 的 key 不能重複,所以在使用 Map 匹配的時候可以有只拿出特定值的效果
這邊有一種濾網的感覺
very_large_map = %{a: 2343, b: 34234, c: 234234, d: 325, e: 355}
%{a: a} = very_large_map
a
#=> 2343
另外,這樣也是可以的,從右邊的 =
執行,開始中間那段可以想成,有 match 成功,但是只有拿出 a,整個的值還是一樣
所以 very_large_map
一樣包含全部的值
very_large_map = %{a: a} = %{a: 2343, b: 34234, c: 234234, d: 325, e: 355}
very_large_map
#=> %{a: 2343, b: 34234, c: 234234, d: 325, e: 355}
a
#=> 2343
在前一篇我們提到,Elixir 允許定義同名且同 arity 的方法 (arity: 變數數量),如下
defmodule Greet do
def hi(%{name: name, level: 1}) do
"你好 #{name}"
end
def hi(%{name: name, level: 2}) do
"你好 #{name}, 會員等級 2 的會員享有半價折扣"
end
def hi(_) do
"你誰?"
end
end
Greet.hi(%{name: Jack, level: 2})
#=> "你好 Jack, 會員等級 2 的會員享有半價折扣"
這個模式滿常見的,可以利用 pattern matching 的特性從 Map 快速濾出要執行的是哪一個方法,
當我們呼叫 Greet.hi(%{name: Jack, level: 2})
的時候,
Elixir 會先找到所有的 hi
方法,並開始從上至下開始一個一個試著 match,
他會先套套看第一個 hi(%{name: name, level: 1})
但發現 level 不符合
接著套套看第二個 hi(%{name: name, level: 2})
,這一個就符合了,
所以會執行這一個方法,忽略剩下的。
如果全部的同名方法都不符合,會出現 MatchError 錯誤。
如果需要的話,可以在最後一個方法使用一個絕對可以 match 的單個變數來把它收起來並做出相對應的回應
如果想要 match 不是預期中的變數,也不需要使用該變數的話,可以用 _
把他接起來,並不再使用。
寫一個可以產生一下結果的 module
member 的格式為 %{name: 字串名稱, level: 數字等級}
,因應會員等級有不同的折扣
如果會員格式錯誤就回傳 "Error"
member_a = %{name: "Jack", level: 3}
Store.discount(member_a)
#=> "30% off!"
member_b = %{name: "Finn", level: 2}
Store.discount(member_b)
#=> "20% off!"
member_c = %{name: "PB", level: 3}
Store.discount(member_c)
#=> "You are PB!, 99% off!"
member_d = {:error, "member not found"}
Store.discount(member_d)
#=> "Error"
defmodule Store do
def discount(%{name: "PB"}) do
# 小陷阱,這個特殊的 VIP 應該要放在比 level 3 前面,
# 要是放在後面就會被 level 3 匹配回應就錯了。
"You are PB!, 99% off!"
end
def discount(%{level: 3}) do
"30% off!"
end
def discount(%{level: 2}) do
"20% off!"
end
def discount(_) do
"Error"
end
end