# Using Kernel.update\_in with a programatically determined key

**URL:** <https://forum.elixirforum.com/t/using-kernel-update-in-with-a-programatically-determined-key/8618>\
**Category:** Questions\
**Tags:** how-to-question\
**Created:** [September 13, 2017, 8:17pm UTC](https://forum.elixirforum.com/t/using-kernel-update-in-with-a-programatically-determined-key/8618 "2017-09-13T20:17:53Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![building39](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/building39/32/27861_2.png) [@building39](https://forum.elixirforum.com/u/building39)\
**Post date:** [September 13, 2017, 8:17pm UTC](https://forum.elixirforum.com/t/using-kernel-update-in-with-a-programatically-determined-key/8618/1 "2017-09-13T20:17:53Z")

</div>

I’m trying to update a deeply nested map (actually, a struct) and am not having any luck. Here’s the scenario:

```elixir
key2 = :"some.key"
Kernel.update_in(state.key1."some.key".key3, &Map.delete(&1, "keyval")) # seems to work fine
Kernel.update_in(state.key1.key2.key3, &Map.delete(&1, "keyval")) # fails, since key2 is a data item, not an atom.
Kernel.update_in(state.key1[key2].key3, &Map.delete(&1, "keyval")) # fails atrociously - beam uses up all available ram, then crashes.

```

I’m sure that I’m doing this entirely wrong - I can’t believe that this would be a bug - same behavior on 1.4.5, 1.5.1 and 1.6.0-dev.

What am I doing wrong?

---

<div class="post-metadata">

**Author:** ![NobbZ](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/nobbz/32/27235_2.png) [@NobbZ](https://forum.elixirforum.com/u/NobbZ)\
**Post date:** [September 13, 2017, 8:36pm UTC](https://forum.elixirforum.com/t/using-kernel-update-in-with-a-programatically-determined-key/8618/3 "2017-09-13T20:36:57Z")

</div>

Small experiment on the REPL, just use `[]` consistently:

```elixir
iex(1)> state = %{foo: %{bar: %{baz: 1}}}
%{foo: %{bar: %{baz: 1}}}
iex(2)> mid = :bar
:bar
iex(3)> update_in(state[:foo][mid][:baz], &(&1+1))
%{foo: %{bar: %{baz: 2}}}
iex(4)> update_in(state, [:foo, mid, :baz], &(&1+1))
%{foo: %{bar: %{baz: 2}}}

```

---

<div class="post-metadata">

**Author:** ![bglusman](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bglusman/32/9459_2.png) [@bglusman](https://forum.elixirforum.com/u/bglusman)\
**Post date:** [September 13, 2017, 8:44pm UTC](https://forum.elixirforum.com/t/using-kernel-update-in-with-a-programatically-determined-key/8618/4 "2017-09-13T20:44:17Z")

</div>

Maybe I’m misunderstanding, but I beleive/thought update\_in relies on Access protocol, and Struct’s don’t implement that, only maps do… I just saw @NobbZ’s post, and I did my own simpler REPL experiment to confrm, and it seems to partly support that, but maybe he understood your issue better? But since I already finished before I saw, I’ll share:

```
iex> defmodule Temp do
(2)> defstruct a: 1
(2)> end
iex> x = %{a: %{b: 1} }
(5)> %{a: %{b: 1}}
iex> y = %Temp{a: %{b: 1} }
(6)> %Temp{a: %{b: 1}}
iex> update_in(x, [:a, :b], &(&1 + 1))
(7)> %{a: %{b: 2}}
iex> update_in(y, [:a, :b], &(&1 + 1))
(8)>** (UndefinedFunctionError) function Temp.get_and_update/3 is undefined (Temp does not implement the Access behaviour)
(8)> Temp.get_and_update(%Temp{a: %{b: 1}}, :a, #Function<3.67985749/1 in Kernel.get_and_update_in/3>)
(8)> (elixir) lib/access.ex:356: Access.get_and_update/3
(8)> (elixir) lib/kernel.ex:1902: Kernel.update_in/3

```

---

<div class="post-metadata">

**Author:** ![building39](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/building39/32/27861_2.png) [@building39](https://forum.elixirforum.com/u/building39)\
**Post date:** [September 13, 2017, 8:52pm UTC](https://forum.elixirforum.com/t/using-kernel-update-in-with-a-programatically-determined-key/8618/5 "2017-09-13T20:52:43Z")

</div>

Yes, that seems to do the trick. Thanks!

---

<div class="post-metadata">

**Author:** ![peerreynders](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/peerreynders/32/5826_2.png) [@peerreynders](https://forum.elixirforum.com/u/peerreynders)\
**Post date:** [September 13, 2017, 9:11pm UTC](https://forum.elixirforum.com/t/using-kernel-update-in-with-a-programatically-determined-key/8618/6 "2017-09-13T21:11:14Z")

</div>

```iex
iex(1)> defmodule Temp do
...(1)> defstruct a: 1
...(1)> end
iex(2)> y = %Temp{a: %{b: 1} }
%Temp{a: %{b: 1}}
iex(3)> update_in(y, [Access.key(:a,%{}), Access.key(:b,0)], &(&1 + 1))
%Temp{a: %{b: 2}}
iex(4)> update_in(%Temp{a: %{}}, [Access.key(:a,%{}), Access.key(:b,0)], &(&1 + 1))
%Temp{a: %{b: 1}}
iex(5)>

```

[Access.key/2](https://hexdocs.pm/elixir/Access.html#key/2)

---

<div class="post-metadata">

**Author:** ![bglusman](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/bglusman/32/9459_2.png) [@bglusman](https://forum.elixirforum.com/u/bglusman)\
**Post date:** [September 14, 2017, 2:45pm UTC](https://forum.elixirforum.com/t/using-kernel-update-in-with-a-programatically-determined-key/8618/7 "2017-09-14T14:45:10Z")

</div>

Thanks @peerreynders hadn’t seend Access.key before, though looks used under the hood by access protocol… but I mentioned the map/struct difference because @building39 said

> [@building39](#):
>
> deeply nested map (actually, a struct)

though to be fair I’m not totally clear whether it’s a struct-nested-in-a-struct, a struct-nested-in-a-map, or something else, but I assume each level needs the Access protocol to use the syntax he was trying to use, without your Access.key addition?

---

<div class="post-metadata">

**Author:** ![peerreynders](https://forum.elixirforum.com/user_avatar/forum.elixirforum.com/peerreynders/32/5826_2.png) [@peerreynders](https://forum.elixirforum.com/u/peerreynders)\
**Post date:** [September 14, 2017, 4:40pm UTC](https://forum.elixirforum.com/t/using-kernel-update-in-with-a-programatically-determined-key/8618/8 "2017-09-14T16:40:39Z")

</div>

> [@bglusman](#):
>
> I assume each level needs the Access protocol to use the syntax he was trying to use, without your Access.key addition?

As stated `Access` exists to provide

> Key-based access to data structures using the data[key] syntax.

The OP also contained attempts to use the [static access operator](https://forum.elixirforum.com/t/the-issue-with-the-static-access-operator-the-dot/4509) which will work on structs that do not implement the `Access` callbacks. But you are correct that the structs would have to implement `Access` callbacks if the supplied list of keys contained “regular keys”.

However [`get_and_update_in/3`](https://hexdocs.pm/elixir/Kernel.html#get_and_update_in/3) and [`get_in/2`](https://hexdocs.pm/elixir/Kernel.html#get_in/2) (and cousins) will work with non-Access structs just fine if they are provided with a list of custom functions - an approach that seems appropriate when interacting with a struct or map “in a programmatic fashion”. Now in most cases the functions generated by `Access.key/2` will be sufficient but it can be instructive to **DIY** just to see what is going on:

```iex
# file: info.ex
#
# Some code trying to demonstrate the mechanics of using "functions instead of keys"
# with Kernel.get_and_update_in/3 and Kernel.get_in/2, attempting to expand on the
# somewhat terse examples found in
# https://hexdocs.pm/elixir/Kernel.html#get_and_update_in/3-examples
# https://hexdocs.pm/elixir/Kernel.html#get_in/2-examples
#
# fn(:get_and_update, data, next) - Clause used for Kernel.get_and_update_in/3
# 1. Obtain "value" of implied key from "data"
# 2. Hand "next" that "value" i.e. {replaced_info, updated_value} = next.(value)
# 3. Update the "data" with the "updated_value" under the implied key to get "updated_data"
# 4. Return {replaced_info, updated_data}
#
# fn(:get, data, next) - Clause used for Kernel.get_in/3
# 1. Obtain "value" of implied key from "data"
# 2. Hand "next" that "value" i.e. info = next.(value)
# 3. Return "info"
#
# Note that Access.key/2 will generate these type of functions automatically - however
# that source accommodates all possible scenarios which can easily obscure
# the fundamental operation. This code focuses on navigating a nested struct.
#
# Essentially both "get_and_update_in" and "get_in" process the keys list recursively
# via the "next" callback handed to each "key function" - which is expected to extract
# the value for the key it is responsible for - invoking "next" with that value
# so that (recursive) navigation on the next level "key function" can continue.
#

defmodule Info do
  defstruct [:name, :age]
  @age_default 0

  def age_access(:get_and_update, info, next) do
    age = Map.get(info, :age, @age_default)
    {old, new} = next.(age) # i.e. age_and_update function
    {old, (Map.put info, :age, new)}
  end
  def age_access(:get, info, next) do
    age = Map.get(info, :age, @age_default)
    next.(age) # i.e. identity function
  end

  def all_list(:get_and_update, info_list, next) when is_list(info_list) do
    old_new_list = (Enum.map info_list, next) # e.g. (get_and_update_in &1, [age_access], age_and_update) on each item
                                              # resulting in {original_age, updated_info} for each item in the list
    :lists.unzip old_new_list # need a list of original ages and a list of updated infos
  end
  def all_list(:get, info_list, next) when is_list(info_list) do
    Enum.map info_list, next # e.g. (get_in &1, [age_access]) on each item
  end
end

defmodule Role do
  defstruct [:name, :info]
  @info_default %Info{name: "unknown", age: 0}

  def info_access(:get_and_update, role, next) do
    info = Map.get(role, :info, @info_default)
    {old, new} = next.(info) # e.g. (get_and_update_in &1, [age_access], age_and_update)
    {old, (Map.put role, :info, new)}
  end
  def info_access(:get, role, next) do
    info = Map.get(role, :info, @info_default)
    next.(info) # e.g. (get_in &1, [age_access])
  end
end

defmodule Trial do
  def run do
    john = %Info{name: "john", age: 27}
    meg = %Info{name: "meg", age: 23}
    info_list = [john, meg]
    owner = %Role{name: "owner", info: john}

    # NOTE: these take the value NOT the key as stated in
    # https://hexdocs.pm/elixir/Kernel.html#get_and_update_in/3
    # This code is consistent with the examples shown where the
    # passed parameter IS a value NOT a key
    age_and_update = fn age -> {age, age + 1} end
    owner_and_replacement = fn owner -> {owner, meg} end # i.e. meg replaces owner

    age_access = &Info.age_access/3
    info_access = &Role.info_access/3
    all_list = &Info.all_list/3

    # IO.inspect (Kernel.get_and_update_in john, [:age], age_and_update)
    IO.inspect (Kernel.get_and_update_in john, [age_access], age_and_update)
    IO.inspect (Kernel.get_in john, [age_access])
    IO.inspect (Kernel.get_and_update_in owner, [info_access, age_access], age_and_update)
    IO.inspect (Kernel.get_and_update_in owner, [info_access], owner_and_replacement)
    IO.inspect (Kernel.get_in owner, [info_access, age_access])
    IO.inspect (Kernel.get_in owner, [info_access])
    IO.inspect info_list
    IO.inspect (Kernel.get_and_update_in info_list, [all_list, age_access], age_and_update)
    IO.inspect (Kernel.get_in info_list, [all_list, age_access])
  end
end

Trial.run()

```
