Questions regarding the Access module and get_and_update_in

My understanding is that the Access module is meant to contain functions that may be used to generate a func that can be used for filtering with nested dictionary structure interrogation, however, I ran into some issues when I was trying to pass my own anonymous function in place of an Access.all/at.

This only occurs with the (dynamic) get_and_update_in rather than get_in. Below is the code (modified from Programming Elixir):


authors = [
  %{ name: "José", language: "Elixir" },
  %{ name: "Matz", language: "Ruby" },
  %{ name: "Larry", language: "Perl" }
]

languages_with_an_r = fn (:get, collection, next_fn) ->
      for row <- collection do
        if String.contains?(row.language, "r") do
          next_fn.(row)
        end
    end
  end

get_and_update_in(authors, [Access.all(), :name], &({&1, &1})

get_and_update_in(authors, [languages_with_an_r, :name], &({&1, &1}))

The first get_and_update_in works, while the second fails citing:

**(FunctionClauseError) no function clause matching in :erl_eval."-inside-an-interpreted-fun-"/3    
   
    The following arguments were given to :erl_eval."-inside-an-interpreted-fun-"/3:
   
        # 1
        :get_and_update
   
        # 2
        [
          %{name: "José", language: "Elixir"},
          %{name: "Matz", language: "Rubyr"},
          %{name: "Larry", language: "Perl"}
        ]
   
        # 3
        #Function<6.66015480/1 in Kernel.get_and_update_in/3>
   
    (stdlib 5.2) :erl_eval."-inside-an-interpreted-fun-"/3
    (stdlib 5.2) erl_eval.erl:963: :erl_eval.eval_fun/8
    iex:6: (file)
    iex:14: (file)

Seems to suggest that the functions output by the Access module functions are of a specific pattern that is acceptable to the get_and_update_in function while other anonymous functions aren’t. At least that’s what I’m understanding.

Can I write my own functions to work with get_and_update_in ?

My second question regards Access.key/1. What is the usecase for this function, is it to make sure a map/struct is being interrogated? I ask because get_in and get_and_update_in allow for direct expression of values as keys in the path list, so what does Access.key/1 do differently here?

get_and_update is calling this function with the first argument :get_and_update, so it’s failing to match because languages_with_an_r only expects :get.

Two things:

  • the docs have all the details, but TLDR using a literal atom only works for maps and kwlists, not structs.
  • Access.key also allows specifying a default, which is handy for updating nested structures
1 Like

Thanks for answering the first part of the question!

I did look at the docs and did not really find an explicit usecase for Access.key/1 (the one where you do not specify a default) aside from making sure that the structure is a map.

That’s why I asked because I’m sure I’m missing something else about the Access.key/1 function.

Is Access.key/1 a function, or is using Access.key with one arg just implementing the default for the second param?

When I type h Access.key/1 I get docs.

While that “direct expression of values as keys in the list” works for a plain map, a struct requires Access.key/2 to dynamically access fields.

Check out this example and error message below:

iex(1)> defmodule Jedi, do: defstruct [:name, :lightsaber]
{:module, Jedi,
 <<...>>,
 %Jedi{name: nil, lightsaber: nil}}

iex(2)> jedis = [%Jedi{name: "Yoda", lightsaber: :green}, %Jedi{name: "Obi Wan", lightsaber: :blue}, %Jedi{name: "Anakin", lightsaber: :blue}]
[
  %Jedi{name: "Yoda", lightsaber: :green},
  %Jedi{name: "Obi Wan", lightsaber: :blue},
  %Jedi{name: "Anakin", lightsaber: :blue}
]

iex(3)> get_and_update_in(jedis, [Access.filter(&(&1.name == "Anakin")), Access.key(:lightsaber)], &({&1, :red}))
{[:blue],
 [
   %Jedi{name: "Yoda", lightsaber: :green},
   %Jedi{name: "Obi Wan", lightsaber: :blue},
   %Jedi{name: "Anakin", lightsaber: :red}
 ]}

iex(4)> get_and_update_in(jedis, [Access.filter(&(&1.name == "Anakin")), :lightsaber], &({&1, :red}))
** (UndefinedFunctionError) function Jedi.get_and_update/3 is undefined (Jedi does not implement the Access behaviour

You can use the "struct.field" syntax to access struct fields. You can also use Access.key!/1 to access struct fields dynamically inside get_in/put_in/update_in)
    Jedi.get_and_update(%Jedi{name: "Anakin", lightsaber: :blue}, :lightsaber, #Function<42.105768164/1 in :erl_eval.expr/6>)
    (elixir 1.16.2) lib/access.ex:384: Access.get_and_update/3
    (elixir 1.16.2) lib/access.ex:887: Access.get_and_update_filter/5
    iex:4: (file)
2 Likes

Ah gotcha, I also get why @al2o3cr was saying it’s in the docs. I’ll read a little on why the other approach doesn’t work with structs.

Another mistake I made was taking Access.key/1 to represent the number of parameters rather than the valid number of arguments (since Access.key/1 uses the default for the second parameter).

Thanks a lot, I was always a bit confused about that section of maps and structs.

Also I thought that the (dict, keys, value) arg pattern alone was enough to satisfy any dynamic requirements but I can see from docs that another step is needed for structs.

you can also implement the Access behavior to allow structs to work just as maps.
I personally prefer to do that, at least for me it makes more sense when dealing with data manipulation.
https://hexdocs.pm/elixir/Access.html#callbacks

1 Like

Oh yeah I like this. Is this the format to which you are referring?