iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 10
1

之前提過,在 OO 語言裡,認為資料及操作一群相關的資料的行為應該放在同一個地方,形成「物件」這種概念。

而在函數式編程裡,卻用完全不同的角度在看待資料結構以及行為之間的關係。資料本身,就只是資料而己,函數式編程更在乎的,是資料的結構。也就是我們如何用適切的方式,來表達這一群資料彼此之間的關係。而各種集合就是用來表達關係的工具。以下我們來介紹最重要的幾種集合類型、其用途,及一些專用函式。

List (串列)

串列是最基本的工具,它通常用來代表一群類似的東西。在之前的章節裡提到過,elixir 裡的串列寫法是 [1, 2, 3, 4, 5] 。但其實這只是顯示跟撰寫時的語法糖而己。當你寫下上面那個串列,他真正形狀長這個樣子:

[1 | [2 | [3 | [4 | [5 | []]]]]

也就是一個首值,加上尾巴的另一個串列,而最末端是一個空串列。這種資料結構叫做 linked list。由於不是其它語言常見的 Array (陣列),因此他沒有直接取第 n 個值的語法,得要透過函式來遍歷。愈長的串列,計算長度跟讀取後面的值時,所需要的時間就會線性的增長。

之前提過,把 | 用在 pattern matching 上可以把串列切成首值及尾部串列:

[h | t] = [1, 2, 3, 4]
#=> h = 1, t = [2, 3, 4]

想連接兩個串列,變成一個新的串列時,要用 ++/2。而 -- 則可以取差集。

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

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

串列裡的數字,如果都在 ASCII 的守備範圍裡時,印出來會變成 charlist:

[104, 101, 108, 108, 111]
#=> 'hello'

List 專用函式

除了剛剛提到的 ++/2--/2 以及 length/1 之外,其它 List 的專用函式會放在 List 模組裡。好用的有 List.wrap/1List.insert_at/3List.pop_at/3List.update_at/3 等等。

Tuple (元組)

這種資料類型學過 Python 的人會比較熟悉。Tuple 可以視為固定長度的串列。語法是 {:ok, "Hello"}。單獨使用時,最常拿來做狀態的 pattern matching。例如絕大多數可能失敗的 Elixir 內建函式操作,都會回傳 {:ok, data} 或是 {:error, message}

例如讀檔案:

 {:ok, content} = File.read(my_file)

這樣 content 只有在成功讀取檔案時才會被綁定。失敗時這行就會直接噴 MatchError。如果想要處理失敗的情況,要用到之後會說明的 case 語法:

case File.read(my_file) do
 {:ok, content} -> do_something(content)
 {:error, msg} -> IO.puts msg
end

如果是個有兩個元素的元組,就稱為 2-tuple,中文翻二元組。同理三個元素的元組叫 3-tuple,三元組。元組通常小於四個,如果太多很可能是你錯用了這個資料結構。

Tuple 專用函式

最常用的函式是 elem/2,讀取特定元素,及 put_elem/3,在特定位置更新元素及 tuple_size/1。而其它的專用函式都放在 Tuple 模組裡。

Keyword list

在函數式語言裡,常常會看到二元組的串列這種資料結構,這在 elixir 中稱為 keyword list。前面那個元組是 key,後面那個元組是 value:

[{:a, 1}, {:b, 2}]

Elixir 也為這種資料結構提供了語法糖:

[a: 1, b:2]

使用在函式宣告或是呼叫的最後一個參數時 ,還可以進一步把方框號省略:

# 宣告
def box(height, width, length, opts \\ color: color, top: top) do
  do_something
end

box(1, 2, 3, color

Keyword list 除了可以拿來做鍵可重覆的有序鍵值對資料結構外,大多拿來做為函式的可選用參數 (optional parameter)。

Keyword list 專用函式

除了可以使用串列能用的所有函式外,其它 Keyword list 專用函式都放在 Keyword 模組下。

Map

Map 也是非常典型的集合資料結構,表示鍵不重覆的無序鍵值對。由於 Erlang 在 2014 的 OTP R17 版才引入這個資料型別,而且初推出時,當內容變多時處理速度極慢。因此雖然 elixir 從最一開始就有 map 資料型別,但舊版的教程常常會教你改用比較難操作,但快很多的 HashDict。不過隨著新版 Erlang 提升 Map 的操作速度後,HashDict 也已經被棄用了。

Map 的語法很像 Ruby 的 Hash 語法,只是前方多了一個百分比的 %{}。連用 atom 當 key 的語法糖都一樣:

%{"a" => 1, "b" => 2}

%{a: 1, b: 2} #=> %{ :a => 1, :b => 2}

Map 的 key 可以是任何值,包含整數、其它資料結構等等。但最常用的還是以字串或 atom 為 key。

與其它程式語言慣例相仿,想要取出特定鍵的值,就會用 []。但若鍵為 atom 的話,也可以用 .

foo = %{"a" => 1, "b" => 2}
foo["b"] #=> 2

bar = %{c: 3, d: 4}
bar[:c] => 3
bar.c => 3

當你有需要對鍵值對 pattern matching 時,就會用 map 而非 keyword list。另外值得一提的是,用空的 map 放在左手側去與任何 map 進行 pattern matching,都視為成功。

%{a: a, b: b} = %{b: 2, a: 1} #=> a = 1, b = 2

%{} = %{a: 1, b: 2, c: 3} # => 回傳 %{a: 1, b: 2, c: 3}

Map 常用函式

處理巢狀的 map 時, Kernel 下的 get_in/2put_in/3update_in/3get_and_update_in/3 會讓你的程式簡潔許多。而其它的函式照慣例都放在 Map 模組下。

重點回顧

  • List 是 linked list,常用來代表一群類似的資料
  • Tuple 常用來做狀態 tag,用於 pattern matching
  • Keyword list 就是 list of 2-tuple,常用於可選參數
  • Map 無序,可以用任何值當做 key。

結束了兩天比較瑣碎、條列式的部份,我們總算有足夠的工具往下走了。今天稍早提到了函數式編程在乎的是資料的結構,這只是故事的一半,另一半,就是如何用函式處理這些資料結構。那就請期待明天的主題囉!

Happy hacking! 明天見。


上一篇
基本型別及運算
下一篇
更泛用的高階函式,與資料轉換的旅程
系列文
函數式編程: 從 Elixir & Phoenix 入門。31

1 則留言

1
taiansu
iT邦新手 5 級 ‧ 2017-12-30 01:42:07

補上許多更新。 XD

我要留言

立即登入留言