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

  • Mastering Elixir
  • André Albuquerque Daniel Caixinha
  • 725字
  • 2021-08-05 10:42:50

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.

主站蜘蛛池模板: 内黄县| 杭锦后旗| 阳曲县| 平度市| 安西县| 镇远县| 子洲县| 久治县| 汽车| 永福县| 丰城市| 普格县| 江源县| 东莞市| 连云港市| 隆子县| 临沂市| 土默特右旗| 阿城市| 宜章县| 体育| 黔西| 弥渡县| 延边| 敦化市| 于都县| 嘉黎县| 日喀则市| 林口县| 厦门市| 内江市| 远安县| 资兴市| 昌邑市| 商河县| 泗洪县| 清原| 上犹县| 安平县| 那曲县| 德庆县|