Skip to content

Commit 2edf718

Browse files
author
José Valim
committed
Do not promote Blank protocol in docs
Signed-off-by: José Valim <jose.valim@plataformatec.com.br>
1 parent 16a14c1 commit 2edf718

File tree

1 file changed

+65
-62
lines changed

1 file changed

+65
-62
lines changed

lib/elixir/lib/kernel.ex

Lines changed: 65 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)