@@ -3716,43 +3716,46 @@ defmodule Kernel do
37163716
37173717 ## Examples
37183718
3719- In Elixir, only `false` and `nil` are considered falsy values.
3720- Everything else evaluates to `true` in `if/2` clauses. Depending
3721- on the application, it may be important to specify a `blank?`
3722- protocol that returns a boolean for other data types that should
3723- be considered "blank". For instance, an empty list or an empty
3724- binary could be considered blank.
3725-
3726- Such protocol could be implemented as follows:
3727-
3728- defprotocol Blank do
3729- @doc "Returns `true` if `data` is considered blank/empty"
3730- def blank?(data)
3719+ In Elixir, we have two verbs for checking how many items there
3720+ are in a data structure: `length` and `size`. `length` means the
3721+ information must be computed. For example, `length(list)` needs to
3722+ traverse the whole list to calculate its length. On the other hand,
3723+ `tuple_size(tuple)` and `byte_size(binary)` do not depend on the
3724+ tuple and binary size as the size information is precomputed in
3725+ the data structure.
3726+
3727+ Although Elixir includes specific functions such as `tuple_size`,
3728+ `binary_size` and `map_size`, sometimes we want to be able to
3729+ retrieve the size of a data structure regardless of its type.
3730+ In Elixir we can write polymorphic code, i.e. code that works
3731+ with different shapes/types, by using protocols. A size protocol
3732+ could be implemented as follows:
3733+
3734+ defprotocol Size do
3735+ @doc "Calculates the size (and not the length!) of a data structure"
3736+ def size(data)
37313737 end
37323738
3733- Now that the protocol is defined it can be implemented. It needs to be
3734- implemented for each Elixir type; for example :
3739+ Now that the protocol can be implemented for every data structure
3740+ the protocol may have a compliant implementation for :
37353741
3736- # Integers are never blank
3737- defimpl Blank, for: Integer do
3738- def blank?(number), do: false
3742+ defimpl Size, for: Binary do
3743+ def size(binary), do: byte_size(binary)
37393744 end
37403745
3741- # The only blank list is the empty one
3742- defimpl Blank, for: List do
3743- def blank?([]), do: true
3744- def blank?(_), do: false
3746+ defimpl Size, for: Map do
3747+ def size(map), do: map_size(map)
37453748 end
37463749
3747- # The only blank atoms are "false" and "nil"
3748- defimpl Blank, for: Atom do
3749- def blank?(false), do: true
3750- def blank?(nil), do: true
3751- def blank?(_), do: false
3750+ defimpl Size, for: Tuple do
3751+ def size(tuple), do: tuple_size(tuple)
37523752 end
37533753
3754- The implementation of the `Blank` protocol would need to be defined for all
3755- Elixir types. The available types are:
3754+ Notice we didn't implement it for lists as we don't have the
3755+ `size` information on lists, rather its value needs to be
3756+ computed with `length`.
3757+
3758+ It is possible to implement protocols for all Elixir types:
37563759
37573760 * Structs (see below)
37583761 * `Tuple`
@@ -3772,11 +3775,11 @@ defmodule Kernel do
37723775
37733776 The real benefit of protocols comes when mixed with structs.
37743777 For instance, Elixir ships with many data types implemented as
3775- structs, like `MapSet`. We can implement the `Blank ` protocol
3778+ structs, like `MapSet`. We can implement the `Size ` protocol
37763779 for those types as well:
37773780
3778- defimpl Blank , for: MapSet do
3779- def blank?(enum_like ), do: Enum.empty?(enum_like )
3781+ defimpl Size , for: MapSet do
3782+ def size(map_set ), do: MapSet.size(map_set )
37803783 end
37813784
37823785 When implementing a protocol for a struct, the `:for` option can
@@ -3786,54 +3789,54 @@ defmodule Kernel do
37863789 defmodule User do
37873790 defstruct [:email, :name]
37883791
3789- defimpl Blank do
3790- def blank? (%User{}), do: false
3792+ defimpl Size do
3793+ def size (%User{}), do: 2 # two fields
37913794 end
37923795 end
37933796
3794- If a protocol is not found for a given type, it will fallback to
3795- `Any`. Protocols that are implemented for maps don't work by default
3796- on structs; look at `defstruct/1` for more information about deriving
3797+ If a protocol implementation is not found for a given type,
3798+ invoking the protocol will raise unless it is configured to
3799+ fallback to `Any`. Conveniences for building implementations
3800+ on top of existing ones are also available, look at `defstruct/1`
3801+ for more information about deriving
37973802 protocols.
37983803
37993804 ## Fallback to any
38003805
38013806 In some cases, it may be convenient to provide a default
3802- implementation for all types. This can be achieved by
3803- setting the `@fallback_to_any` attribute to `true` in the protocol
3807+ implementation for all types. This can be achieved by setting
3808+ the `@fallback_to_any` attribute to `true` in the protocol
38043809 definition:
38053810
3806- defprotocol Blank do
3811+ defprotocol Size do
38073812 @fallback_to_any true
3808- def blank? (data)
3813+ def size (data)
38093814 end
38103815
3811- The `Blank ` protocol can now be implemented for `Any`:
3816+ The `Size ` protocol can now be implemented for `Any`:
38123817
3813- defimpl Blank , for: Any do
3814- def blank? (_), do: true
3818+ defimpl Size , for: Any do
3819+ def size (_), do: 0
38153820 end
38163821
3817- One may wonder why such behaviour (fallback to any) is not the default one.
3818-
3819- It is two-fold: first, the majority of protocols cannot
3820- implement an action in a generic way for all types; in fact,
3821- providing a default implementation may be harmful, because users
3822- may rely on the default implementation instead of providing a
3823- specialized one.
3824-
3825- Second, falling back to `Any` adds an extra lookup to all types,
3826- which is unnecessary overhead unless an implementation for `Any` is
3827- required.
3822+ Although the implementation above is arguably not a reasonable
3823+ one. For example, it makes no sense to say a PID or an Integer
3824+ have a size of 0. That's one of the reasons why `@fallback_to_any`
3825+ is an opt-in behaviour. For the majority of protocols, raising
3826+ an error when a protocol is not implemented is the proper behaviour.
38283827
38293828 ## Types
38303829
38313830 Defining a protocol automatically defines a type named `t`, which
38323831 can be used as follows:
38333832
3834- @spec present?(Blank.t) :: boolean
3835- def present?(blank) do
3836- not Blank.blank?(blank)
3833+ @spec print_size(Size.t) :: :ok
3834+ def print_size(data) do
3835+ IO.puts(case Size.size(data) do
3836+ 0 -> "data has no items"
3837+ 1 -> "data has one item"
3838+ n -> "data has #{ n } items"
3839+ end)
38373840 end
38383841
38393842 The `@spec` above expresses that all types allowed to implement the
@@ -3872,12 +3875,12 @@ defmodule Kernel do
38723875 all implementations are known up-front, Elixir provides a feature
38733876 called protocol consolidation. For this reason, all protocols are
38743877 compiled with `debug_info` set to `true`, regardless of the option
3875- set by `elixirc` compiler. The debug info though may be removed
3876- after consolidation.
3878+ set by `elixirc` compiler. The debug info though may be removed after
3879+ consolidation.
38773880
3878- For more information on how to apply protocol consolidation to
3879- a given project , please check the functions in the `Protocol`
3880- module or the `mix compile.protocols` task.
3881+ Protocol consolidation is applied by default to all Mix projects.
3882+ For applying consolidation manually , please check the functions in
3883+ the `Protocol` module or the `mix compile.protocols` task.
38813884 """
38823885 defmacro defprotocol ( name , do_block )
38833886
0 commit comments