View Source Lens2.Deeply (Lens 2 v0.2.1)
Operations that work with lenses. The API is close to the familiar get
, put
, update
one.
Note, though, these differences:
Most APIs with
get
(likeMap
andKeyword
) return a single value from a container. Lenses point at zero or more places within a container, so the natural "read" operation is to return a collection (specifically, aList
). Naming that operationget
invites mistakes, so the fundamental "read" operation isLens2.Deeply.get_all/2
. In the case where you know there's a single value being returned, you can useLens2.Deeply.get_only/2
to avoid having to pick a value out of a singleton list.In those Elixir core APIs, it's the operations that decides what to do about missing keys and the like. Consider
Map.update/4
(use a default) andMap.update!/3
(raise an error). In this lens package, it's the lens that decides. See, for example,Lens2.Lenses.Keyed.key/1
,Lens2.Lenses.Keyed.key?/1
, andLens2.Lenses.Keyed.key!/1
. Or see this page in the tutorial.When you have structs, you can add lenses to the module interface so that you can write code like:
Deeply.put(%Container{...}, Container.place(...), ...)
In the reasonably-common case where the module's lens function takes no argument, you can just use the bare atom name:
Deeply.put(%Container{...}, :place, ...)
See this page for more.
The functions in this module have simple implementations because lenses are
compatible with the Access
behaviour. For example,
Lens2.Deeply.put/3
is just a wrapper around a call to
put_in/3
:
def put(container, lens, value),
do: put_in(container, [lens], value)
You can use the Kernel
functions if you prefer. Don't forget the bracket!
For convenience and backwards compatibility, this package also provides the Lens 1 functions to_list/2
, and one!/2
.
Summary
Functions
Call a function (for side effects) on each pointed-at value.
Returns a list of the values that the lens points at.
Retrieve all pointed-at values and, at the same time, update them.
Return the single value the lens points at.
Return the single value the lens points at.
Put a specific value into all the places the lens points at.
Returns a list of the values that the lens points at.
Apply the given function to every value pointed at by the lens, returning an updated container.
Types
@type lens_or_atom() :: Lens2.lens() | atom()
Functions
@spec each(Lens2.container(), lens_or_atom(), (Lens2.value() -> no_return())) :: :ok
Call a function (for side effects) on each pointed-at value.
iex> container = %{a: 1, b: 2, c: 3}
iex> lens = Lens.keys([:a, :c])
iex> Deeply.each(container, lens, fn value ->
...> send(self(), "sending #{value}")
...> end)
iex> assert_receive("sending 1")
iex> assert_receive("sending 3")
Any return value from the fun
is ignored.
@spec get_all(Lens2.container(), lens_or_atom()) :: [Lens2.value()]
Returns a list of the values that the lens points at.
iex> lens = Lens.map_values
iex> %{a: 1, b: 2, c: 3} |> Deeply.get_all(lens) |> Enum.sort
[ 1, 2, 3]
get_all
produces its result with get_in/2
.
@spec get_and_update( Lens2.container(), lens_or_atom(), (Lens2.value() -> {Lens2.value(), Lens2.updated_value()}) ) :: {[Lens2.value()], Lens2.container()}
Retrieve all pointed-at values and, at the same time, update them.
The return value is a tuple with two elements:
A list (as with
get_all/2
) of the values before the update.The entire container, updated as with
update/3
.iex> lens = Lens.keys([:a, :c]) ...> fun = fn value -> ...> {value, value * 1000} ...> end iex> %{a: 1, b: 2, c: 3} |> Deeply.get_and_update(lens, fun) { [ 1, 3], %{a: 1000, b: 2, c: 3000} }
get_and_update
produces its result with get_and_update_in/3
.
@spec get_only(Lens2.container(), lens_or_atom()) :: Lens2.value()
Return the single value the lens points at.
This calls get_all/2
and unwraps the single value in the resulting list.
If that list has a different number of elements, a MatchError
will be raised.
iex> lens = Lens.key(:a)
iex> %{a: 1, b: 2, c: 3} |> Deeply.get_only(lens)
1
@spec one!(Lens2.container(), lens_or_atom()) :: Lens2.value()
Return the single value the lens points at.
This is the name the get_only/2
function had in Lens 1. I prefer get_only/2
,
but if you prefer this name, more power to you.
This calls get_all/2
and unwraps the single value in the resulting list.
If that list has a different number of elements, a MatchError
will be raised.
iex> lens = Lens.key(:a)
iex> %{a: 1, b: 2, c: 3} |> Deeply.one!(lens)
1
@spec put(Lens2.container(), lens_or_atom(), Lens2.value()) :: Lens2.container()
Put a specific value into all the places the lens points at.
iex> lens = Lens.keys([:a, :c])
iex> %{a: 1, b: 2, c: 3} |> Deeply.put(lens, :NEW)
%{ a: :NEW, b: 2, c: :NEW}
put
produces its result with put_in/3
.
@spec to_list(Lens2.container(), lens_or_atom()) :: [Lens2.value()]
Returns a list of the values that the lens points at.
This is the name the get_all/2
function had in Lens 1. I prefer get_all/2
,
note least because if I forget and use Deeply.get
, I get a helpful error message:
Lens2.Deeply.get/2 is undefined or private. Did you mean:
* get_all/2
* get_only/2
However, to_list
also has its a strong legacy in the Elixir
tradition, so you choose.
iex> lens = Lens.map_values
iex> %{a: 1, b: 2, c: 3} |> Deeply.to_list(lens) |> Enum.sort
[ 1, 2, 3]
get_all
produces its result with get_in/2
.
@spec update( Lens2.container(), lens_or_atom(), (Lens2.value() -> Lens2.updated_value()) ) :: Lens2.container()
Apply the given function to every value pointed at by the lens, returning an updated container.
iex> lens = Lens.keys([:a, :c])
iex> updater = & &1 * 1000
iex> %{a: 1, b: 2, c: 3} |> Deeply.update(lens, updater)
%{ a: 1000, b: 2, c: 3000}
This corresponds to map
in the original
Lens
package. The name
has been changed for consistency with Access
.
update
produces its result with update_in/3
.