View Source Lens2.Lenses.Bi (Lens 2 v0.2.1)
Lens makers for the BiMap
bidirectional map package.
The package contains both BiMap
and BiMultiMap
. These lenses work with
both.
iex> lens = Lens.Bi.from_key(:a)
iex> Deeply.get_all(BiMap.new(a: 5), lens)
[5]
iex> Deeply.get_all(BiMultiMap.new(a: 5), lens)
[5]
Both map types allow a fast "reverse lookup": using a value to find the corresponding key.
iex> bimap = BiMap.new(%{1 => "1111", 2 => "2222"})
iex> BiMap.get(bimap, 1) # key to value
"1111"
iex> BiMap.get_key(bimap, "1111") # value to key
1
A BiMap
differs importantly from a map in that it requires
values, not just keys, to be unique. This can cause some confusion
as Deeply.put
can cause existing bindings to disappear.
iex> bimap = BiMap.new(a: 5, b: 6)
iex> Deeply.put(bimap, Lens.Bi.from_key!(:b), 5)
BiMap.new(b: 5) # where did `:a` go?
A BiMultiMap
doesn't have that restriction. A given key may be associated
with multiple values and a given value may be associated with multiple keys.
However, a single key/value pair can only appear once:
iex> multi = BiMultiMap.new(a: 5, b: 6, b: 5, b: 5, b: 5, b: 5)
BiMultiMap.new(a: 5, b: 6, b: 5) # only one copy of {:b, 5}
iex> Deeply.put(multi, Lens.Bi.from_key!(:b), 5)
BiMultiMap.new(a: 5, b: 5) # duplicate {:b, 5} pair is removed
This module violates the convention of using names like key
to mean
using a key to obtain a value. I found myself typing Lens.key
(etc.) when
I meant Lens.Bi.key
. The from_key*
form is used to go from a key to a value;
the to_key*
form is used to go from a value to a key.
Summary
Functions
Return a lens that points at all key/value pairs.
Return a lens that points at all keys.
Return a lens that points at all values.
Point at all values associated with a key. (BiMap
will have only
one, BiMultiMap
may have several.)
Like from_key/1
and from_key?/1
except that the lens will raise an error for a missing key.
Return a lens that points at the value or values of a single key, ignoring missing keys.
Like from_key/1
but takes a list of keys.
Like from_key!/1
but takes a list of keys.
Like from_key?/1
but takes a list of keys.
Return a lens that points at the key(s) associated with a given value. nil
is used if there is no such value.
Return a lens that points at the key(s) associated with a given value. Raises an error if there is no such value.
Return a lens that points at the key(s) associated with a given value, provided there is any such value.
Like to_key/1
but takes a list of values.
Like to_key!/1
but takes a list of values.
Like to_key?/1
but takes a list of values.
Restore results into whichever the appropriate type is.
Functions
Return a lens that points at all key/value pairs.
Deeply.get_all
will return a List
of {key, value} tuples
.
iex> bimap = BiMap.new(%{1 => 10, 2 => 20})
iex> Deeply.get_all(bimap, Lens.Bi.all) |> Enum.sort
[{1, 10}, {2, 20}]
Lens2.Lenses.Enum.all/0
would do the same. However, it would return a
list in the case of Deeply.update
, whereas all/0
wraps the
result in an appropriate bidirectional map:
iex> bimap = BiMap.new(%{1 => 10, 2 => 20})
iex> tweak_tuple = fn {k, v} -> {k-1, v+1} end
iex> Deeply.update(bimap, Lens.all, tweak_tuple) |> Enum.sort
[{0, 11}, {1, 21}] # List is because we used `Lens.all`, not `Lens.Bi.all`
...>
iex> Deeply.update(bimap, Lens.Bi.all, tweak_tuple)
BiMap.new(%{0 => 11, 1 => 21})
Deeply.put
is weird and useless because you can only create a singleton map (as duplicate
{key, value}
tuples will be removed):
iex> multi = BiMultiMap.new(a: 1, b: 2)
iex> Deeply.put(multi, Lens.Bi.all, {:z, 9}) # adds the key/value pair twice
BiMultiMap.new(z: 9)
@spec all_keys() :: Lens2.lens()
Return a lens that points at all keys.
This is useful for descending into complex keys. Consider a
spatial BiMap that connects {x, y}
tuples to some values
representing physical objects at that position.
iex> bimap = BiMap.new(%{{0, 0} => "some data", {1, 1} => "other data"})
iex> Deeply.update(bimap, Lens.Bi.all_keys,
...> fn {x, y} -> {x + 1, y + 1} end)
iex> BiMap.new(%{{1, 1} => "some data", {2, 2} => "other data"})
Note that all the updates are done before the result map is formed. You needn't fear
that updating the {0, 0}
tuple will wipe out the existing {1, 1}
tuple.
@spec all_values() :: Lens2.lens()
Return a lens that points at all values.
iex> container = BiMultiMap.new(a: 1, b: 2, a: 2)
iex> Deeply.get_all(container, Lens.Bi.all_values) |> Enum.sort
[1, 2, 2] # Note duplicates
Note that using put
with all_values
is profoundly useless for a BiMap
, as
only one key/value pair can be retained – and you can't even predict
which one it will be!
iex> bimap = BiMap.new(a: 1, b: 2, c: 3, d: 4, e: 5)
iex> updated = Deeply.put(bimap, Lens.Bi.all_values, :NEW)
iex> assert [:NEW] == BiMap.values(updated)
...>
iex> [key] = BiMap.keys(updated)
iex> assert key in [:a, :b, :c, :d, :e]
...> # On my machine, today, it turns out to be `:e`.
Deeply.put
works with BiMultiMaps
:
iex> multi = BiMultiMap.new(a: 1, b: 2, c: 3, d: 4, e: 5)
iex> Deeply.put(multi, Lens.Bi.all_values, :NEW)
BiMultiMap.new(a: :NEW, b: :NEW, c: :NEW, d: :NEW, e: :NEW)
@spec from_key(any()) :: Lens2.lens()
Point at all values associated with a key. (BiMap
will have only
one, BiMultiMap
may have several.)
As with Lens2.Lenses.Keyed.key/1
, a missing key is represented with nil
:
iex> bimap = BiMap.new(a: 1, b: 2)
...> lens = Lens.both(Lens.Bi.from_key(:a), Lens.Bi.from_key(:MISSING))
iex> Deeply.get_all(bimap, lens) |> Enum.sort
[1, nil]
This lens can introduce a missing key-value pair:
iex> Deeply.put(BiMap.new, Lens.Bi.from_key(:missing), :NEW)
BiMap.new(%{missing: :NEW})
@spec from_key!(any()) :: Lens2.lens()
Like from_key/1
and from_key?/1
except that the lens will raise an error for a missing key.
This works the same as Lens2.Lenses.Keyed.key!/1
.
iex> multi = BiMultiMap.new(a: 1)
iex> Deeply.put(multi, Lens.Bi.from_key!(:a), :NEW)
BiMultiMap.new(a: :NEW)
iex> Deeply.put(multi, Lens.Bi.from_key!(:missing), :NEW)
** (ArgumentError) key `:missing` not found in: BiMultiMap.new([a: 1])
@spec from_key?(any()) :: Lens2.lens()
Return a lens that points at the value or values of a single key, ignoring missing keys.
This works the same as Lens2.Lenses.Keyed.key?/1
.
iex> bimap = BiMap.new(a: 1, b: 2)
iex> lens = Lens.both(Lens.Bi.from_key?(:a), Lens.Bi.from_key?(:MISSING))
iex> Deeply.get_all(bimap, lens)
[1]
Unlike from_key/1
, the returned lens cannot be used to create a missing key:
iex> Deeply.put(BiMap.new, Lens.Bi.from_key?(:missing), :NEW)
BiMap.new
@spec from_keys([any()]) :: Lens2.lens()
Like from_key/1
but takes a list of keys.
The value of a missing key is treated as nil
.
iex> bimap = BiMap.new(a: 2)
iex> lens = Lens.Bi.from_keys([:a, :missing])
iex> updater = fn
...> nil -> "newly added"
...> x -> x * 1111
...> end
iex> Deeply.update(bimap, lens, updater)
BiMap.new(a: 2222, missing: "newly added")
@spec from_keys!([any()]) :: Lens2.lens()
Like from_key!/1
but takes a list of keys.
Any missing key raises an error.
iex> bimap = BiMap.new(a: 2)
iex> lens = Lens.Bi.from_keys!([:a, :missing])
iex> Deeply.get_only(bimap, lens)
** (ArgumentError) key `:missing` not found in: BiMap.new([a: 2])
@spec from_keys?([any()]) :: Lens2.lens()
Like from_key?/1
but takes a list of keys.
Missing keys are ignored.
iex> bimap = BiMap.new(a: 2)
iex> lens = Lens.Bi.from_keys?([:a, :missing])
iex> Deeply.update(bimap, lens, & &1*1111)
BiMap.new(a: 2222)
iex> Deeply.get_only(bimap, lens)
2
@spec to_key(any()) :: Lens2.lens()
Return a lens that points at the key(s) associated with a given value. nil
is used if there is no such value.
iex> multi = BiMultiMap.new(a: 1, b: 2, a: 2)
iex> Deeply.update(multi, Lens.Bi.to_key(2), &inspect/1)
BiMultiMap.new([{:a, 1}, {":b", 2}, {":a", 2}])
iex> Deeply.update(multi, Lens.Bi.to_key(38), &inspect/1)
BiMultiMap.new([{:a, 1}, {:b, 2}, {:a, 2}, {"nil", 38}])
Whether being able to create a key that's some variant of nil
is actually useful, well...
@spec to_key!(any()) :: Lens2.lens()
Return a lens that points at the key(s) associated with a given value. Raises an error if there is no such value.
iex> bimap = BiMap.new(a: 1)
iex> Deeply.get_all(bimap, Lens.Bi.to_key!(1))
[:a]
iex> Deeply.get_all(bimap, Lens.Bi.to_key!(11111))
** (ArgumentError) value `11111` not found in: BiMap.new([a: 1])
@spec to_key?(any()) :: Lens2.lens()
Return a lens that points at the key(s) associated with a given value, provided there is any such value.
If there is no such value, nothing is done.
iex> bimap = BiMap.new(a: 1)
iex> Deeply.get_all(bimap, Lens.Bi.to_key?(1))
[:a]
iex> Deeply.get_all(bimap, Lens.Bi.to_key?(11111))
[]
You can't create a new key-value pair with this function. See to_key/1
.
@spec to_keys([any()]) :: Lens2.lens()
Like to_key/1
but takes a list of values.
iex> container = BiMultiMap.new(a: 1, b: 2, a: 2)
iex> lens = Lens.Bi.to_keys([1, 2])
iex> Deeply.update(container, lens, &inspect/1)
BiMultiMap.new([{":a", 1}, {":b", 2}, {":a", 2}])
@spec to_keys!([any()]) :: Lens2.lens()
Like to_key!/1
but takes a list of values.
Any missing value raises an error.
iex> bimap = BiMap.new(a: 2)
iex> lens = Lens.Bi.to_keys!([2, "missing value"])
iex> Deeply.get_all(bimap, lens)
** (ArgumentError) value `"missing value"` not found in: BiMap.new([a: 2])
@spec to_keys?([any()]) :: Lens2.lens()
Like to_key?/1
but takes a list of values.
Missing keys are ignored.
iex> bimap = BiMap.new(%{1 => "value"})
iex> lens = Lens.Bi.to_keys?(["value", "some missing value"])
iex> Deeply.update(bimap, lens, & &1*1111)
BiMap.new(%{1111 => "value"})
iex> Deeply.get_all(bimap, lens)
[1]
@spec update_appropriately(Lens2.lens()) :: Lens2.lens()
Restore results into whichever the appropriate type is.
Suppose all_values/0
didn't exist, and you were writing it as a
pipeline. That involves treating the container as an Enum
, getting
key/value tuples, and then taking the second tuple element. However, that's
a problem for update, as you get a list of tuples back:
iex> lens = Lens.all |> Lens.at(1)
iex> Deeply.update(BiMultiMap.new(a: 1, b: 2), lens, & &1 * 1111)
[{:a, 1111}, {:b, 2222}] # more commonly printed as [a: 1111, b: 2222]
Outside this package, you'd use Lens2.Lenses.Enum.update_into/2
to "coerce the type." You can use that here too:
iex> lens = Lens.update_into(BiMap.new, Lens.all |> Lens.at(1))
iex> Deeply.update(BiMultiMap.new(a: 1, b: 2), lens, & &1 * 1111)
BiMap.new(a: 1111, b: 2222)
Notice the resulting container is a BiMap
even though the original
container was a BiMultiMap
. If you only ever use one of the two,
that may be OK. But if you want to write a lens that works with
both, use this function. It creates the empty container to match
the original type:
iex> lens = Lens.Bi.update_appropriately(Lens.all |> Lens.at(1))
iex> Deeply.update(BiMultiMap.new(a: 1, b: 2), lens, & &1 * 1111)
BiMultiMap.new(a: 1111, b: 2222)
iex> Deeply.update(BiMap.new(a: 1, b: 2), lens, & &1 * 1111)
BiMap.new(a: 1111, b: 2222)
This is in fact the implementation of all_values/0
.