|
| 1 | +# Derives the `Enumerable` for `struct` |
| 2 | + |
| 3 | +We use the `c:TypedStructor.Plugin.after_definition/2` callback to |
| 4 | +generate the `Enumerable` implementation for the struct. |
| 5 | +We implement `Enumerable` callbacks exclusively using the fields that are defined. |
| 6 | + |
| 7 | +Let's start! |
| 8 | + |
| 9 | +## Implementation |
| 10 | +```elixir |
| 11 | +defmodule MyPlugin do |
| 12 | + use TypedStructor.Plugin |
| 13 | + |
| 14 | + @impl TypedStructor.Plugin |
| 15 | + defmacro after_definition(definition, _opts) do |
| 16 | + quote bind_quoted: [definition: definition] do |
| 17 | + keys = Enum.map(definition.fields, &Keyword.fetch!(&1, :name)) |
| 18 | + |
| 19 | + defimpl Enumerable do |
| 20 | + def count(enumerable), do: {:ok, Enum.count(unquote(keys))} |
| 21 | + def member?(enumerable, element), do: {:ok, Enum.member?(unquote(keys), element)} |
| 22 | + |
| 23 | + def reduce(enumerable, acc, fun) do |
| 24 | + # The order of fields is guaranteed to align with the sequence in which they are defined. |
| 25 | + unquote(keys) |
| 26 | + |> Enum.map(fn key -> {key, Map.fetch!(enumerable, key)} end) |
| 27 | + |> Enumerable.List.reduce(acc, fun) |
| 28 | + end |
| 29 | + |
| 30 | + # We don't support this |
| 31 | + def slice(_enumerable), do: {:error, __MODULE__} |
| 32 | + end |
| 33 | + end |
| 34 | + end |
| 35 | +end |
| 36 | +``` |
| 37 | + |
| 38 | +## Usage |
| 39 | +```elixir |
| 40 | +defmodule User do |
| 41 | + use TypedStructor |
| 42 | + |
| 43 | + typed_structor do |
| 44 | + plugin MyPlugin |
| 45 | + |
| 46 | + field :name, String.t(), enforce: true |
| 47 | + field :age, integer(), enforce: true |
| 48 | + end |
| 49 | +end |
| 50 | +``` |
| 51 | + |
| 52 | +```elixir |
| 53 | +iex> user = %User{name: "Phil", age: 20} |
| 54 | +%User{name: "Phil", age: 20} |
| 55 | +# the order of fields is deterministic |
| 56 | +iex> Enum.map(user, fn {key, _value} -> key end) |
| 57 | +[:name, :age] |
| 58 | +# we got a deterministic ordered Keyword list |
| 59 | +iex> Enum.to_list(user) |
| 60 | +[name: "Phil", age: 20] |
| 61 | +``` |
| 62 | + |
| 63 | +> #### Bonus {: .info} |
| 64 | +> Additionally, we gain the bonus of having a deterministic order of fields, |
| 65 | +> determined by the sequence in which they are defined. |
0 commit comments