I've found a need to write a lazy version of `Access.key/2` when traversing nested structures where providing a default value might be expensive.
```elixir
defmodule Book, do: defstruct([:name, :isbn_13, :author])
defmodule Author, do: defstruct([:name, :related_works])
defmodule RelatedWorks, do: defstruct([:books, :papers, :music])
book = %Book{
name: "Programming Erlang, Second Edition",
isbn_13: "978-1-937785-53-6",
author: %Author{name: "Joe Armstrong", related_works: nil}
}
related_works =
Kernel.get_in(
book,
[
Access.key(:author, fn -> lookup_author(book) end),
Access.lazy_key(:related_works, fn -> lookup_related_works(book) end),
Access.key(:books, [])
]
)
```
For my use-case I also needed to differentiate between `nil` fields on structs vs maps, so I added the third parameter for an anonymous function that takes in the result of `Map.fetch/2` and returns whether or not to substitute the value with a default.
Here's the full implementation:
```elixir
@spec lazy_key(
key,
(-> term),
(map | struct, {:ok, term} | :error -> boolean)
) ::
access_fun(
data :: struct | map,
current_value :: term
)
def lazy_key(
key,
default_fn \\ fn -> nil end,
use_default? \\ fn
data, {:ok, nil} when is_struct(data) -> :replace
_, {:ok, value} -> {:keep, value}
_, :error -> :replace
end
) do
fn
:get, data, next ->
value = fetch_or_default(data, key, default_fn, use_default?)
next.(value)
:get_and_update, data, next ->
value = fetch_or_default(dt ata, key, default_fn, use_default?)
case {data, next.(value)} do
{_data, {get, update}} -> {get, Map.put(data, key, update)}
{data, :pop} when is_struct(data) -> {value, Map.put(data, key, nil)}
{data, :pop} -> {value, Map.delete(data, key)}
end
end
end
defp fetch_or_default(data, key, default_fn, use_default?) do
with fetch_result <- Map.fetch(data, key),
{:keep, value} <- use_default?.(data, fetch_result) do
value
else
:replace -> default_fn.()
end
end
```
I was about to open a PR to add in the implementation but then I saw that I should propose the idea here first (it's my first time submitting a proposal).
I'd love to hear your thoughts on the idea.
– Ocean