Implementing Access

I just got this struct error:

(UndefinedFunctionError) function A.B.get_and_update/3 is undefined (A.B does not implement the Access behaviour)

So, I set about trying to figure out how to implement Access, but I could find no information on how to do that.

  1. Why wouldn’t a struct implement Access by default?

  2. How does one implement Access?

  1. There are structs, which you don’t want to access like a map, e.g. the Task struct or the MapSet struct. The first one you probably hardly want to look into first place and for the second one the fields, where actual items are stored are an implementation detail. Also updating a MapSet not using MapSet functions is not really want you want to do if you want to keep it being a set.

  2. The Access module has some callbacks you need to implement. If you want to have the struct behave as a Map you can just delegate to Map's functions.

1 Like

If you find yourself implementing Access for many different structs, I’d recommend taking a look at https://github.com/codedge-llc/accessible.

Just add use Accessible to your module and it will implement the Access callbacks for you.

1 Like

Okay, that sounds simple enough–except there’s that pesky @behaviour tag (@behavior produces a warning), and then there’s this warning:

defmodule A.B do
  @behaviour Access
  defstruct personal_info: %{}, other: []

  def fetch(term, key), do: Map.fetch(term, key)

  def get_and_update(data, key, func) do
    Map.get_and_update(data, key, func)
  end

  def pop(data, key), do: Map.pop(data, key)
end

defmodule My do

  def go do
    map = %{
      greeting: "hello world",
      active: true,
      description: "player points",
      id: 118,
      inserted_at: ~N[2018-08-26 19:48:22.501445],
      reserved: %A.B{
        personal_info: %{
          game_id: "b796cbe9-0bb6-4aaf-98b0-5da81c337208",
          player_id: "8ffb69ce-9a6b-44a6-8e8f-c069235d2d31",
          player_name: "Lebron James"
        },
        other: [%{label: "Player Points", type: "text"}]
      },
      type: "NBA",
      updated_at: ~N[2018-08-26 19:48:22.504193]
    }

    put_in(map, ~w{reserved personal_info player_id}a, 12345)
  end
    
   
end

~/elixir_programs$ iex my.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]

warning: function get/3 required by behaviour Access is not implemented (in module A.B)
  my.exs:1

Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)

Okay, even though there’s no mention of get() anywhere in the callback section of the Access docs:

defmodule A.B do
  @behaviour Access
  defstruct personal_info: %{}, other: []

  def fetch(term, key), do: Map.fetch(term, key)

  def get_and_update(data, key, func) do
    Map.get_and_update(data, key, func)
  end

  def pop(data, key), do: Map.pop(data, key)

  def get(map, key, default), do: Map.get(map, key, default)

end

defmodule My do

  def go do
    map = %{
      greeting: "hello world",
      active: true,
      description: "player points",
      id: 118,
      inserted_at: ~N[2018-08-26 19:48:22.501445],
      reserved: %A.B{
        personal_info: %{
          game_id: "b796cbe9-0bb6-4aaf-98b0-5da81c337208",
          player_id: "8ffb69ce-9a6b-44a6-8e8f-c069235d2d31",
          player_name: "Lebron James"
        },
        other: [%{label: "Player Points", type: "text"}]
      },
      type: "NBA",
      updated_at: ~N[2018-08-26 19:48:22.504193]
    }

    put_in(map, ~w{reserved personal_info player_id}a, 12345)
  end
    
end

In iex:

~/elixir_programs$ iex my.exs
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.6.6) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> My.go
%{
  active: true,
  description: "player points",
  greeting: "hello world",
  id: 118,
  inserted_at: ~N[2018-08-26 19:48:22.501445],
  reserved: %A.B{
    other: [%{label: "Player Points", type: "text"}],
    personal_info: %{
      game_id: "b796cbe9-0bb6-4aaf-98b0-5da81c337208",
      player_id: 12345,
      player_name: "Lebron James"
    }
  },
  type: "NBA",
  updated_at: ~N[2018-08-26 19:48:22.504193]
}

iex(2)> 

Success.

So, yeah, someone needs to post a tutorial somewhere then link to it when beginners want to know how to implement Access to get at deeply nested data in a struct.

And, why shouldn’t that be as simple as:

@behaviour Access, do: Map

or:

defstruct personal_info: %{}, other: [], __delegate__: Map
2 Likes