這是 w3HexSchool 鼠年全馬鐵人挑戰 Week 7 唷
這是 Elixir 從零開始 系列 06 唷
前言
Pattern matching 又稱模式比對,是 Elixir 內很重要的一個環節也是一個特色,很多地方都會使用到,強烈建議必讀必看喔
The match operator
如果是在一般的程式語言中,比如說 C# 或 Java 中,=
在這段程式碼內的意思就是
把 22 指派給變數
val
1 | int val = 22; |
但是當我們換到 Elixir 中,就沒有所謂指派給,這種運算子,打個比方,在之前的文章中常常會看到以下類似的程式碼
1 | iex(1)> person = {"Bob", 25} |
在這裡,在等號左邊的 person
我們稱呼它為 pattern,在等號右邊的 {"Bob", 25}
則可以稱呼為 term,person = {"Bob", 25}
這整句話的意思呢,我們可以這樣說
We match the variable
pseron
to the right-side term{"Bob", 25}
或者我們可這樣想
想像成拼圖,當等號左邊(
pseron
)與等號右邊({"Bob", 25}
)長得很像時,就可以合理推測兩邊的外型是一樣的(同樣都是 tuple ),同時間,外型一樣就代表內容也是一樣的,內容一樣就等於pseron
的內容就是{"Bob", 25}
以上就一個模式比對的解釋,可能聽不懂?沒關係!讓我們來舉更多更多的例子來解釋
Fail matching
模式比對都是要比對成功的,那如果是一個失敗的比對呢?這左右兩邊的外型長的根本不一樣,自然也就匹配不起來囉
1 | iex(4)> {name, age} = "hello world" |
再一發
1 | iex(5)> {amount, amount, amount} = {127, 127, 127} |
這裡的 amount 被匹配為 127,當下一行再次做比對的時候會發現失敗,因為左邊的 pattern 形狀是必須三個變數都長得一樣才行,有一個 127 不等於 1 就不行啦
Matching tuples
更多的例子來了
1 | iex(1)> {name, age} = {"Bob", 25} |
利用模式比對來比較的話,name
的值為 "Bob"
,age
的值為 25
再來一個
1 | iex(1)> {_, time} = :calendar.local_time() |
Matching constants
這很無聊,就是自己比對自己,得到的答案會是自己
1 | iex(1)> 1 = 1 |
但我們今天把 person
匹配給 {:person, "Bob", 25}
,並且自己比對自己,我們可以發現,比對成功!
1 | iex(2)> person = {:person, "Bob", 25} |
Matching lists
1 | iex(1)> [first, second, third] = [1, 2, 3] |
Matching maps
1 | iex(1)> %{name: name, age: age} = %{name: "Bob", age: 25} |
要注意一點是,在 map 中可以不用全部匹配,以下這樣部分匹配也行,但沒有 key 是匹配不起來的
1 | iex(4)> %{age: age} = %{name: "Bob", age: 25} |
1 | iex(6)> %{age: age, works_at: works_at} = %{name: "Bob", age: 25} |
Matching with functions
我們來看一個函式長怎麼樣子
1 | def my_fun(arg1, arg2) do |
在這裡的 arg1 和 arg2 是 patterns,而 term 就是我們在呼叫它的時候輸入的變數
1 | defmodule TestModule do |
我們可以這樣
1 | iex(1)> TestModule.my_fun(2, 3) |
但我們不能這樣,這樣會造成匹配失敗
1 | iex(1)> TestModule.my_fun({2, 3}) |
接下來我們來介紹 Matching with functions 的一個延伸應用,在一般的程式語言中,如果我們要使用條件分支,我們或許可以這樣寫
1 | function test(x){ |
但到了 Elixir 中,我們必須改成變一下寫法,我們可以使用 Multiclause functions 來達到一樣的效果,如下
1 | defmodule TestNum do |
所謂的 Multiclause functions 其實就是多個 Matching with function,藉由 pattern matching 讓每一個條件都可以進入一個 function 中處理事情,達到一樣條件分支的效果喔
Multiclause functions
在 Elixir 中允許 overload,也就是同一名稱函式可以出現多次,差別只在輸入的變數不一樣,假設今天我們要計算面積,不管是圓型或是長方形都要使用同一個函式來計算,那我們可以這樣做
1 | defmodule Geometry do |
我們可以看到,在 Geometry 這個 Module 中定義了 rectangle、square 和 circle,唯獨沒有定義 triangle,所以當我們在 iex 上嘗試想要輸入 {:triangle, 1, 2, 3}
的時候會失敗,因為我們匹配不到任何東西
1 | iex(1)> Geometry.area({:rectangle, 4, 5}) |
那好假設我們不想要出現 error 要什麼修改 Module 呢
1 | defmodule Geometry do |
我們新增一個 pattern,讓任何奇怪形狀的輸入都可進到 area(unknown)
來,這樣一來所有的 input 皆可被捕捉到啦
注意,
area(unknown)
不可以放在 Module 的第一行當作第一個函式,這樣所有的 input 都只會跑到第一個 pattern 內,而不會進入第二第三個 pattern
1 | iex(1)> Geometry.area({:square, 5}) |
Multiclause lambdas
一個正常的匿名函式(lambda)長這樣
1 | iex(1)> double = fn x -> x*2 end |
讓我們讓它變得更豐富一點,變成 Multiclause lambdas
1 | iex(3)> test_num = |
那我們可以測試一下
1 | iex(4)> test_num.(-1) |