iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 30
2

雖然我也很想趕快發個感想文結業,但是題目講不完只好再寫一篇。這一篇想來示範所謂的新的角度看程式的一個例子,就是不需要封裝與繼承,還是可以有多型這回事。

一點點理論

多形 (polymorphism) 在學術上,分為參數多型 (parametric polymorphism) 及特設多型 (ad-hoc polymorphism)。參數多型適用於靜態型別的語言,例如 Haskell、OCaml 等等,物件導向的 Java、C# 則是透過泛型來實作。因為 Elixir 是動態型別語言,所以基本上不適用。

而特設多型還分成 close ad-hoc polymorphism 及 open ad-hoc polymorphism。前者在 Elixir 中,就是函式的 pattern matching。而 open ad-hoc polymorphism,就是今天所要講的 Protocol。

實作

多型具體講起來,就是依資料型別 (物件型別) 的不同,會有不一樣的行為。回想一下 Enum 模組,像是 Enum.map/2Enum.reduce/3,第一個參數可以使用各種型別的集合,這些函式都能正確的遍歷每個集合。這就是使用了 Protocol 機制達成的。

例如我們想要實作一個 Find.next/1 函式,會依不同的型別找到下一個值,首先要先宣告一個 Protocol:

defprotocol Find do
  def next(data)
end

defprotocol 後面接模組名稱,並在 do...end 區塊裡放入這個 protocol 所擁有的函式名稱及參數,這些函式沒有本體。如果你會寫 Java 的話,可以想像它有點像是 Interface。

而不同型別要實作此 protocol 時,要使用 defimpl

defimpl Find, for: Integer do
  def next(i), do: i + 1
end

defimpl Find, for: BitString do
  def next(str) do
    str
    |> String.to_charlist
    |> Enum.map(&(&1 + 1))
    |> List.to_string
  end
end

這些 defimpl 若是內建型別,習慣上會放在跟 defprotocol 同一個檔案裡。而若是自訂的 struct 的話,則會放在宣告 struct 的檔案下。

這麼一來,Find.next/1 就會依傳入的型別不同,進行不同的動作了:

iex> Find.next(1)
2
iex> Find.next("a")
"b"
iex> Find.next("abc")
"bcd"

所有的型別: Any

如果想要實作對其它所有的型別 protocol 的話,可以使用 For: Any

defimpl Next, for: Any do
  def next(_), do: :error
end

下一篇就是感想文了。Happy hacking!


上一篇
Elixir 中的平行運算
下一篇
30 |> days |> beyond
系列文
函數式編程: 從 Elixir & Phoenix 入門。31

尚未有邦友留言

立即登入留言