官术网_书友最值得收藏!

Protocols

Throughout this introductory chapter, we've mentioned a couple of times that Elixir has protocols, with Enumerable being one of the examples. In this section, we'll dive into protocols and even define our own!

Protocols, like the behaviours we've seen in the last section, define a set of functions that have to be implemented. In that sense, both constructs serve as a way to achieve polymorphism in Elixirbeing able to display multiple forms of behavior, but all linked to a single interface. While behaviours define a set of functions that a module needs to implement, and are thus tied to a module, protocols define a set of functions that a data type must implement. This means that, with protocols, we have data type polymorphism, and we're able to write functions that behave differently depending on the type of their arguments.

Let's now see how we can create a new protocol. We'll pick up, and extend, the example present in the official Getting Started guide (at http://elixir-lang.github.io/getting-started/protocols.html). We will define a Size protocol, which will be implemented by each data type. To define a new protocol, we use the defprotocol construct:

$ cat examples/size.ex
defprotocol Size do
@doc "Calculates the size of a data structure"
def size(data)
end

We're stating that our Size protocol expects the data types that will implement it must define a size/1 function, where the argument is the data structure we want to know the size of.

You can use the @doc directive to add documentation to this function, as you normally do with named functions inside modules. We can now define the implementation of this protocol for the data types we're interested in, using the defimpl construct:

$ cat examples/size_implementations_basic_types.ex
defimpl Size, for: BitString do
def size(string), do: byte_size(string)
end

defimpl Size, for: Map do
def size(map), do: map_size(map)
end

defimpl Size, for: Tuple do
def size(tuple), do: tuple_size(tuple)
end
We didn't define an implementation for the lists, as in Elixir, size is usually used for data structures that have their size precomputed. For types where we have to compute this on demand, such as lists, the length term is used instead of size. This is further observable by looking at the name of the function used to get the dimension of a list: Kernel.length/1.

With this defined, we can see our protocol in action:

iex> Size.size("a string")
8
iex> Size.size(%{a: "b", c: "d"})
2
iex> Size.size({1, 2, 3})
3

If we try to use our protocol on a type that doesn't have an implementation defined, an error is raised:

iex> Size.size([1, 2, 3, 4])
** (Protocol.UndefinedError) protocol Size not implemented for [1, 2, 3, 4]
You can define an implementation for a protocol on all Elixir data types: Atom, BitString, Float, Function, Integer, Tuple, List, Map, PID, Port, and Reference. Note that BitString is used for the binary type as well.

Having to implement a protocol for all types may quickly become monotonous and exhausting. You can define a fallback behavior for types that don't implement your protocol by implementing the protocol for Any. Let's do this for our Size protocol:

$ cat examples/size_implementation_any.ex
defimpl Size, for: Any do
def size(_), do: 0
end

You have to define the desired behavior when a type doesn't implement your protocol. In this case, we're saying that it has a size of 0 (which might not make sense, since the data type may have a size different than 0, but let's ignore that detail).

We now have two options for this implementation to be used: Either mark the modules where we want this fallback behavior with @derive [Size] (the List module, for instance), or use @fallback_to_any true in the definition of our Size protocol. The former is more laborious as you have to annotate each module that you want to assume the behavior for Any, while the latter is simpler since you make it work on all data types just by changing the definition of your protocol. In the Elixir community, explicitness is usually preferred, and, as such, you're more likely to see the @derive approach in Elixir projects.

While implementing protocols for Elixir's data types already opens a world of possibilities, we can only fully utilize Elixir's extensibility when we mix them with structs. We haven't yet talked about structs, so we'll introduce them in the next section.

主站蜘蛛池模板: 马山县| 南康市| 蓝山县| 陆河县| 鄂托克前旗| 浦城县| 北川| 靖安县| 寿阳县| 柘荣县| 兴文县| 山东| 依兰县| 德州市| 梅州市| 老河口市| 宣汉县| 永福县| 祁东县| 武夷山市| 乃东县| 丹棱县| 云梦县| 聊城市| 南平市| 襄垣县| 哈巴河县| 阿巴嘎旗| 商南县| 彭山县| 呼玛县| 米泉市| 施秉县| 滁州市| 隆尧县| 保定市| 黔江区| 伊金霍洛旗| 措勤县| 上虞市| 大渡口区|