From 19105a3bd2437250113776164ddf1209a65da387 Mon Sep 17 00:00:00 2001 From: Andrea Leopardi Date: Fri, 11 May 2018 00:45:35 +0200 Subject: Polish docs around Access and Access-related functions in Kernel --- lib/elixir/lib/access.ex | 87 +++++++++++++++++++++++++++++++----------------- lib/elixir/lib/kernel.ex | 41 ++++++++++++++--------- 2 files changed, 83 insertions(+), 45 deletions(-) diff --git a/lib/elixir/lib/access.ex b/lib/elixir/lib/access.ex index e8ff7222b..bfa455fb7 100644 --- a/lib/elixir/lib/access.ex +++ b/lib/elixir/lib/access.ex @@ -2,33 +2,33 @@ defmodule Access do @moduledoc """ Key-based access to data structures. - Elixir supports three main key-value constructs, keywords, - maps and structs, and two mechanisms to access those keys, - by brackets via `data[key]`, and by dot-syntax, via `data.field`. + Elixir supports three main key-value constructs: keywords, + maps, and structs. It also supports two mechanisms to access those keys: + by brackets (via `data[key]`) and by dot-syntax (via `data.field`). - Next we will briefly recap the key-value constructs and then + In the next section we will briefly recap the key-value constructs and then discuss the access mechanisms. ## Key-value constructs Elixir provides three main key-value constructs, summarized below: - * keyword lists - they are lists of two element tuples where + * keyword lists - they are lists of two-element tuples where the first element is an atom. Commonly written in the `[key: value]` syntax, they support only atom keys. Keyword lists are used almost exclusively to pass options to functions and macros. They keep the user ordering and allow duplicate - keys. Powered by the `Keyword` module. + keys. See the `Keyword` module. - * maps - they are the "go to" key-value data structure in Elixir - capable of supporting billions of keys of any type. They are - written in the `%{key => value}` syntax and also support the + * maps - they are the "go to" key-value data structure in Elixir. + They are capable of supporting billions of keys of any type. They are + written using the `%{key => value}` syntax and also support the `%{key: value}` syntax when the keys are atoms. They do not have any specified ordering and do not allow duplicate keys. - Powered by the `Map` module. + See the `Map` module. * structs - they are named maps with a pre-determined set of keys. - They are defined with `defstruct/1` and written in the + They are defined with `defstruct/1` and written using the `%StructName{key: value}` syntax. ## Key-based accessors @@ -40,7 +40,8 @@ defmodule Access do The `data[key]` syntax is used to access data structures with a dynamic number of keys, such as keywords and maps. The key can - be of any type and it returns nil if the key does not exist: + be of any type. The bracket-based access syntax returns `nil` + if the key does not exist: iex> keywords = [a: 1, b: 2] iex> keywords[:a] @@ -62,12 +63,17 @@ defmodule Access do iex> put_in(users["john"][:age], 28) %{"john" => %{age: 28}, "meg" => %{age: 23}} - Furthermore, the bracket access transparently ignores `nil` values: + Furthermore, the bracket-based access syntax transparently ignores + `nil` values. When trying to access anything on a `nil` value, `nil` + is returned: iex> keywords = [a: 1, b: 2] iex> keywords[:c][:unknown] nil + iex> nil[:a] + nil + Internally, `data[key]` translates to `Access.get(term, key, nil)`. Developers interested in implementing their own key-value data structures can implement the `Access` behaviour to provide the @@ -77,8 +83,8 @@ defmodule Access do ### Dot-based syntax The `data.field` syntax is used exclusively to access atom fields - in maps and structs. If the field accessed does not exist, it - raises an error. This is a deliberate decision: since all of the + in maps and structs. If the accessed field does not exist, an error is + raised. This is a deliberate decision: since all of the fields in a struct are pre-determined, structs support only the dot-based syntax and not the access one. @@ -107,22 +113,20 @@ defmodule Access do ## Nested data structures Both key-based access syntaxes can be used with the nested update - functions in `Kernel`, such as `Kernel.get_in/2`, `Kernel.put_in/3`, - `Kernel.update_in/3` and `Kernel.get_and_update_in/3`. + functions and macros in `Kernel`, such as `Kernel.get_in/2`, `Kernel.put_in/3`, + `Kernel.update_in/3`, `Kernel.pop_in/2`, and `Kernel.get_and_update_in/3`. - For example, to update a map inside another map of dynamic values, - one can do: + For example, to update a map inside another map: iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}} - iex> put_in users["john"].age, 28 + iex> put_in(users["john"].age, 28) %{"john" => %{age: 28}, "meg" => %{age: 23}} - This module provides convenience functions for traversing other - structures, like tuples and lists, to be used alongside `Kernel.put_in/2` - and others. + structures, like tuples and lists. These functions can be used + in all the `Access`-related functions and macros in `Kernel`. - For instance, given a user map with `:name` and `:languages` keys, + For instance, given a user map with the `:name` and `:languages` keys, here is how to deeply traverse the map and convert all language names to uppercase: @@ -190,7 +194,7 @@ defmodule Access do def get(structure, key, default) do case fetch(structure, key) do {:ok, value} -> value - :error -> default + :error -> default end end @@ -208,9 +212,12 @@ defmodule Access do If the passed function returns `{get_value, update_value}`, the return value of this callback should be `{get_value, new_data}`, where: - - `get_value` is the retrieved value (which can be operated on before being returned) - - `update_value` is the new value to be stored under `key` - - `new_data` is `data` after updating the value of `key` with `update_value`. + + * `get_value` is the retrieved value (which can be operated on before being returned) + + * `update_value` is the new value to be stored under `key` + + * `new_data` is `data` after updating the value of `key` with `update_value`. If the passed function returns `:pop`, the return value of this callback must be `{value, new_data}` where `value` is the value under `key` @@ -258,6 +265,15 @@ defmodule Access do Returns `{:ok, value}` where `value` is the value under `key` if there is such a key, or `:error` if `key` is not found. + + ## Examples + + iex> Access.fetch(%{name: "meg", age: 26}, :name) + {:ok, "meg"} + + iex> Access.fetch([ordered: true, on_timeout: :exit], :timeout) + :error + """ @spec fetch(container, term) :: {:ok, term} | :error @spec fetch(nil_container, any) :: :error @@ -299,6 +315,17 @@ defmodule Access do Returns the value under `key` if there is such a key, or `default` if `key` is not found. + + ## Examples + + iex> Access.get(%{name: "john"}, :name, "default name") + "john" + iex> Access.get(%{name: "john"}, :age, 25) + 25 + + iex> Access.get([ordered: true], :timeout) + nil + """ @spec get(container, term, term) :: term @spec get(nil_container, any, default) :: default when default: var @@ -437,8 +464,8 @@ defmodule Access do The returned function uses the default value if the key does not exist. This can be used to specify defaults and safely traverse missing keys: - iex> get_in(%{}, [Access.key(:user, %{}), Access.key(:name)]) - nil + iex> get_in(%{}, [Access.key(:user, %{name: "meg"}), Access.key(:name)]) + "meg" Such is also useful when using update functions, allowing us to introduce values as we traverse the data structure for updates: diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index b16a73710..673ba9f17 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -2060,8 +2060,8 @@ defmodule Kernel do iex> get_in(users, ["unknown", :age]) nil - When one of the keys is a function, the function is invoked. - In the example below, we use a function to get all the maps + When one of the keys is a function that takes three arguments, the function + is invoked. In the example below, we use a function to get all the maps inside a list: iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}] @@ -2132,23 +2132,29 @@ defmodule Kernel do @doc """ Gets a value and updates a nested structure. - `data` is a nested structure (ie. a map, keyword + `data` is a nested structure (that is, a map, keyword list, or struct that implements the `Access` behaviour). The `fun` argument receives the value of `key` (or `nil` if `key` - is not present) and must return a two-element tuple: the "get" value - (the retrieved value, which can be operated on before being returned) - and the new value to be stored under `key`. The `fun` may also - return `:pop`, implying the current value shall be removed - from the structure and returned. + is not present) and must return one of the following values: + + * a two-element tuple `{get_value, new_value}`. In this case, + `get_value` is the retrieved value which can possibly be operated on before + being returned. `new_value` is the new value to be stored under `key`. - It uses the `Access` module to traverse the structures + * `:pop`, which implies that the current value under `key` + should be removed from the structure and returned. + + This function uses the `Access` module to traverse the structures according to the given `keys`, unless the `key` is a function. If a key is a function, the function will be invoked - passing three arguments, the operation (`:get_and_update`), - the data to be accessed, and a function to be invoked next. + passing three arguments: + + * the operation (`:get_and_update`) + * the data to be accessed + * a function to be invoked next This means `get_and_update_in/3` can be extended to provide custom lookups. The downside is that functions cannot be stored @@ -2158,8 +2164,8 @@ defmodule Kernel do This function is useful when there is a need to retrieve the current value (or something calculated in function of the current value) and - update it at the same time. For example, it could be used to increase - the age of a user by one and return the previous age in one pass: + update it at the same time. For example, it could be used to read the + current age of a user while increasing it by one in one pass: iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}} iex> get_and_update_in(users, ["john", :age], &{&1, &1 + 1}) @@ -2171,7 +2177,7 @@ defmodule Kernel do iex> users = [%{name: "john", age: 27}, %{name: "meg", age: 23}] iex> all = fn :get_and_update, data, next -> - ...> Enum.map(data, next) |> :lists.unzip + ...> data |> Enum.map(next) |> Enum.unzip() ...> end iex> get_and_update_in(users, [all, :age], &{&1, &1 + 1}) {[27, 23], [%{name: "john", age: 28}, %{name: "meg", age: 24}]} @@ -2182,7 +2188,7 @@ defmodule Kernel do The `Access` module ships with many convenience accessor functions, like the `all` anonymous function defined above. See `Access.all/0`, - `Access.key/2` and others as examples. + `Access.key/2`, and others as examples. """ @spec get_and_update_in( structure :: Access.t(), @@ -2222,6 +2228,11 @@ defmodule Kernel do In case any entry returns `nil`, its key will be removed and the deletion will be considered a success. + + iex> users = %{"john" => %{age: 27}, "meg" => %{age: 23}} + iex> pop_in(users, ["jane", :age]) + {nil, %{"john" => %{age: 27}, "meg" => %{age: 23}}} + """ @spec pop_in(data, nonempty_list(Access.get_and_update_fun(term, data) | term)) :: {term, data} when data: Access.container() -- cgit v1.2.1