How the put_in/2 works?

Reading the guides at this point http://elixir-lang.org/getting-started/keywords-and-maps.html#nested-data-structures We have an example that we can modify a specific data in a map using the put_in/2.

I’m curious how Elixir makes this work:

iex> users = [
  john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},
  mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}
]
[john: %{age: 27, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
 mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}]

iex> users = put_in users[:john].age, 31
[john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
 mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}]

How Elixir returns the whole map? If we do not pass it to the function?

Thanks in advance.

2 Likes

put_in/2 is a macro, not a function. The arguments are passed unevaluated, and the put_in/2 implementation operates on their syntactic representations instead. Since the first argument is users[:john].age, the macro actually does have access to users.

3 Likes

Well, put_in/2 is not a function but a macro, so it can do any magic it wants with the AST of your first parameter before expanding itself.

You can find the definiton on github: https://github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/kernel.ex#L2012

3 Likes

Thanks @jmitchell and @NobbZ ! Awesome. I’ll read more about the macros to understand how it works!

2 Likes

A good place to start is the Quote and unquote guide, and then the following section on macros. You may also find it helpful to study how the short-circuiting or AST macro works before trying to tackle put_in/2. In particular, notice that the right side is only evaluated when the left side is falsy, whereas if || were a function all of its arguments would be evaluated before the function body.

iex(1)> IO.puts "left" || IO.puts "right"
left
:ok
iex(2)> false || IO.puts "right"
right
:ok
3 Likes