Qqwy
Updating structs: Map.put vs %Foo{oldfoo | new: value} vs put_in
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum.
In the (beta version of the) book, they update structs using Map.put/3. I’ve found myself doing the same in my own code. @hubertlepicki astutely observed that this opens up the opportunity for mistyped field names when updating your structs: This is totally allowed, the result of Map.put/3 will then be a map (instead of an instance of your struct).
Example:
iex> defmodule Foo do
iex> defstruct bar: 1 , baz: 2
iex> end
iex> foo = %Foo{}
iex> foo |> Map.put(:qux, 4)
%{__struct__: Foo, bar: 1, baz: 2, qux: 4} # <- note that it is printed as a map.
The other two methods known to me to update structs are:
- The special struct update syntax,
%Foo{oldfoo | bar: 4}. This will result in a compile-time error when wrong field names are used. Drawback: It cannot be piped. -
put_in/2:newfoo = put_in(foo.bar, 4). This will also result in a compile-time error when a wrong field name is used. Drawback: It, too, cannot be piped.
put_in/3 (as in: newfoo = put_in(foo, [:bar], 4) would be able to be piped, but it is not available for structs; it only works on things that implement the Access protocol.
So what is the best way to update structs in practice? Am I missing any method in this list? Would it be a good idea to have a version of put_in that works on structs and is pipeable?
Most Liked
hubertlepicki
My eyes hurt. I do the same, but I die a bit every time I have to write it.
NobbZ
The special Form %name{} pipes pretty well I think, you need to wrap in an anonymous funtion though):
iex(1)> defmodule S do
...(1)> defstruct f: 0
...(1)> end
{:module, S, <<...>>, %S{f: 0}}
iex(2)> s = %S{}
%S{f: 0}
iex(3)> 1 |> (&%S{s | f: &1}).()
%S{f: 1}
iex(4)> s |> (&%S{&1 | f: &1.f + 5}).()
%S{f: 5}
josevalim
@michalmuskala I always thought about put_existing and put_existing! (which neatly mirrors put_new).







