Closed
Description
Elixir and Erlang/OTP versions
1.17.0
Operating system
any
Current behavior
Currently, the documentation suggests updating structures using the map syntax::
%{john | name: "Jane"}
This approach has a significant drawback, struct integrity checking is performed at runtime. At best (and only if the code hints that the variable is a structure), the compiler issues a warning if there is a typo in the field name:
Interactive Elixir (1.15.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> defmodule User do
...(1)> defstruct name: "John", age: 27
...(1)> end
{:module, User,
<<70, 79, 82, 49, 0, 0, 7, 248, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 247,
0, 0, 0, 22, 11, 69, 108, 105, 120, 105, 114, 46, 85, 115, 101, 114, 8, 95,
95, 105, 110, 102, 111, 95, 95, 10, 97, ...>>, %User{name: "John", age: 27}}
iex(2)> defmodule TestModule1 do
...(2)> def some_function(user) do
...(2)> %{user | non_existing_field: :some_value}
...(2)> end
...(2)> end
{:module, TestModule1,
<<70, 79, 82, 49, 0, 0, 5, 244, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 226,
0, 0, 0, 21, 18, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 77, 111,
100, 117, 108, 101, 49, 8, 95, 95, 105, ...>>, {:some_function, 1}}
iex(3)> TestModule1.some_function(%User{})
** (KeyError) key :non_existing_field not found
iex:4: TestModule1.some_function/1
iex:3: (file)
iex(3)> defmodule TestModule2 do
...(3)> def some_function(%User{} = user) do
...(3)> %{user | non_existing_field: :some_value}
...(3)> end
...(3)> end
warning: undefined field "non_existing_field" in expression:
# iex:5
%{user | non_existing_field: :some_value}
expected one of the following fields: __struct__, age, name
where "user" was given the type %User{} in:
# iex:4
%User{} = user
Conflict found at
iex:5: TestModule2.some_function/1
{:module, TestModule2,
<<70, 79, 82, 49, 0, 0, 6, 16, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 236,
0, 0, 0, 21, 18, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 77, 111,
100, 117, 108, 101, 50, 8, 95, 95, 105, ...>>, {:some_function, 1}}
iex(4)> TestModule2.some_function(%User{})
** (KeyError) key :non_existing_field not found
iex:5: TestModule2.some_function/1
iex:4: (file)
however, if we use the struct syntax for update, the validation is done at compile time:
iex(4)> defmodule TestModule3 do
...(4)> def some_function(user) do
...(4)> %User{user | non_existing_field: :some_value}
...(4)> end
...(4)> end
error: unknown key :non_existing_field for struct User
iex:6: TestModule3.some_function/1
** (CompileError) iex: cannot compile module TestModule3 (errors have been logged)
(elixir 1.15.4) src/elixir_module.erl:182: anonymous fn/9 in :elixir_module.compile/7
iex:4: (file)
Expected behavior
The documentation should recommend the most error-proof method of updating structures:
%User{john | name: "Jane"}
The documentation should also provide a justification of this choice. It might be worth mentioning this in the code anti-patterns chapter as well.
Metadata
Metadata
Assignees
Labels
No labels