16-協議
16-協議
協議和結構體
迴歸大眾
內建協議
協議是實現Elixir多型性的重要機制。任何資料型別只要實現了某協議,那麼該協議的分發就是可用的。 讓我們看個例子。
在Elixir中,只有false和nil被認為是false。其它的值都被認為是true。
根據程式需要,有時需要一個blank?
協議,返回一個布林值,以說明該引數是否為空。
舉例來說,一個空列表或者空二進位制可以被認為是空的。
我們可以如下定義協議:
defprotocol Blank do
@doc "Returns true if data is considered blank/empty"
def blank?(data)
end
這個協議期待一個函式blank?
,它接受一個待實現的引數。
我們為不同的資料型別實現這個協議:
# Integers are never blank
defimpl Blank, for: Integer do
def blank?(_), do: false
end
# Just empty list is blank
defimpl Blank, for: List do
def blank?([]), do: true
def blank?(_), do: false
end
# Just empty map is blank
defimpl Blank, for: Map do
# Keep in mind we could not pattern match on %{} because
# it matches on all maps. We can however check if the size
# is zero (and size is a fast operation).
def blank?(map), do: map_size(map) == 0
end
# Just the atoms false and nil are blank
defimpl Blank, for: Atom do
def blank?(false), do: true
def blank?(nil), do: true
def blank?(_), do: false
end
我們可以為所有內建資料型別實現協議:
- 原子
- BitString
- 浮點型
- 函式
- 整型
- 列表
- 圖
- PID
- Port
- 引用
- 元祖
現在手邊有了一個定義並被實現的協議,如此使用之:
iex> Blank.blank?(0)
false
iex> Blank.blank?([])
true
iex> Blank.blank?([1, 2, 3])
false
給它傳遞一個並沒有實現該協議的資料型別,會導致報錯:
iex> Blank.blank?("hello")
** (Protocol.UndefinedError) protocol Blank not implemented for "hello"
16.1-協議和結構體
協議和結構體一起使用能夠大大加強Elixir的可擴充套件性。
在前面幾章中我們知道,儘管結構體就是圖,但是它們和圖並不共享各自協議的實現。
像前幾章一樣,我們先定義一個名為User
的結構體:
iex> defmodule User do
...> defstruct name: "john", age: 27
...> end
{:module, User,
<<70, 79, 82, ...>>, {:__struct__, 0}}
然後看看:
iex> Blank.blank?(%{})
true
iex> Blank.blank?(%User{})
** (Protocol.UndefinedError) protocol Blank not implemented for %User{age: 27, name: "john"}
結構體沒有使用協議針對圖的實現,而是使用它自己的協議實現:
defimpl Blank, for: User do
def blank?(_), do: false
end
如果願意,你可以定義你自己的語法來檢查一個user為不為空。 不光如此,你還可以使用結構體建立更強健的資料型別,比如佇列,然後實現所有相關的協議,比如列舉(Enumerable)或檢查是否為空。
有些時候,程式設計師們希望給結構體提供某些預設的協議實現。因為顯式給所有結構體都實現某些協議實在是太枯燥了。 這引出了下一節“迴歸大眾”(falling back to any)的說法。
16.2-迴歸大眾
能夠給所有型別提供預設的協議實現肯定是很方便的。在定義協議時,把@fallback_to_any
設定為true
即可:
defprotocol Blank do
@fallback_to_any true
def blank?(data)
end
現在這個協議可以被這麼實現:
defimpl Blank, for: Any do
def blank?(_), do: false
end
現在,那些我們還沒有實現Blank
協議的資料型別(包括結構體)也可以來判斷是否為空了。
16.3-內建協議
Elixir自帶了一些內建協議。在前面幾章中我們討論過列舉模組,它提供了許多方法。 只要任何一種資料結構它實現了Enumerable協議,就能使用這些方法:
iex> Enum.map [1, 2, 3], fn(x) -> x * 2 end
[2,4,6]
iex> Enum.reduce 1..3, 0, fn(x, acc) -> x + acc end
6
另一個例子是String.Chars
協議,它規定了如何將包含字元的資料結構轉換為字串型別。
它暴露為函式to_string
:
iex> to_string :hello
"hello"
注意,在Elixir中,字串插值操作呼叫的是to_string
函式:
iex> "age: #{25}"
"age: 25"
上面程式碼能工作是因為數字型別實現了String.Chars
協議。如果傳進去的是元組就會報錯:
iex> tuple = {1, 2, 3}
{1, 2, 3}
iex> "tuple: #{tuple}"
** (Protocol.UndefinedError) protocol String.Chars not implemented for {1, 2, 3}
當想要列印一個比較複雜的資料結構時,可以使用inspect
函式。該函式基於協議Inspect
:
iex> "tuple: #{inspect tuple}"
"tuple: {1, 2, 3}"
Inspect協議用來將任意資料型別轉換為可讀的文字表述。IEx用來列印表示式結果用的就是它:
iex> {1, 2, 3}
{1,2,3}
iex> %User{}
%User{name: "john", age: 27}
記住,習慣上來說,無論何時,頭頂#號被插的值,會被表現成一個不合語法的字串。 在轉換為可讀的字串時丟失了資訊,因此別指望還能從該字串取回原來的那個物件:
iex> inspect &(&1+2)
"#Function<6.71889879/1 in :erl_eval.expr/5>"
Elixir中還有些其它協議,但本章就講這幾個比較常用的。下一章將講講Elixir中的錯誤捕捉以及異常。
相關文章
- 【網路協議】IP協議、ARP協議、RARP協議協議
- RTSP協議、RTMP協議、HTTP協議的區別協議HTTP
- 【網路協議】UDP協議協議UDP
- Gossip協議也叫Epidemic協議(流行病協議)Go協議IDE
- IP協議(網路層協議)協議
- 16-排序排序
- 協議協議
- 頁面連結跳轉--指定協議,半協議,無協議協議
- 【網路協議】TCP協議簡介協議TCP
- 路由協議與閘道器協議路由協議
- 淺談WebSocket協議、WS協議和WSS協議原理及關係Web協議
- 二進位制協議 VS 文字協議協議
- 生成樹協議與多生成樹協議協議
- 匯流排協議系列——USART協議初探協議
- 16-繼承繼承
- 16-強化
- Memcached 協議協議
- mysql協議MySql協議
- raft協議Raft協議
- HTTP 協議HTTP協議
- swift協議Swift協議
- OSPF協議協議
- 【TLS協議】TLS協議
- XModem協議協議
- [HTTP協議]HTTP協議
- SNMP協議協議
- Kerberos協議ROS協議
- SMB協議協議
- CMPP協議協議
- SSH 協議協議
- SFTP協議FTP協議
- 雲協議協議
- XMPP協議協議
- SNMP 協議協議
- PXE協議協議
- 優惠協議協議
- BGP協議協議
- TCP協議TCP協議