The following code results in a warning that is difficult for me to understand:
defmodule Mod do
def hello(name) do
map = Map.new(words: ["world"])
IO.inspect(length(map.words), label: :before)
map = %{map | words: [name | map.words]}
IO.inspect(length(map.words), label: :after)
end
end
This yields the following warning:
warning: incompatible types:
%{words: [var1 | var2]} !~ %{words: var2}
in expression:
# lib/mod.ex:7
map.words
where "map" was given the type %{words: [var1 | var2], words: var2, optional(dynamic()) => dynamic()} in:
# lib/mod.ex:6
map = %{map | words: [name | map.words]}
where "map" was given the type %{words: [var1 | var2], optional(dynamic()) => dynamic()} (due to calling var.field) in:
# lib/mod.ex:7
map.words
HINT: "var.field" (without parentheses) implies "var" is a map() while "var.fun()" (with parentheses) implies "var" is an atom()
Conflict found at
lib/mod.ex:7
The warning goes away if:
-
The first IO.inspect with
label: :before
is commented out. Then the following two
lines (6 and 7) are not longer at odds with each othertype
-wise. -
OR: Leave the IO.inspect as-is, but change the line following it (the map update) to
one of the following:
map = %{map | words: [name] ++ map.words}
# or
map = %{map | words: [name | map[:words]]}
-
OR: Use a struct instead of a dynamic map.
-
OR: change the first IO.inspect to inspect
map[:words]
instead ofmap.words
So the warning is about conflicting types for %{words: [var1 | var2]} !~ %{words: var2}
- line 6 →
%{words: [var1 | var2], words: var2, optional(dynamic()) => dynamic()}
- line 7 →
%{words: [var1 | var2], optional(dynamic()) => dynamic()}
both mention optional(dynamic()) => dynamic()
, the first seemingly only for var2
in the prepending of the list, the latter for the list itself?
In both cases, map is a (dynamic) map, the atom key :words
’s value is a list so I expected
both to be of the same type, since [var1 | var2]
is prepending var1
to the list var2
,
much like the [var1] ++ var2
variant. This is what confuses me.
It all seems related to using the dot-notation to access an atom key in a dynamic map.
The docs say:
To access atom keys, one may also use the map.key
notation. Note that map.key
will raise a KeyError
if the map
doesn’t contain the key :key
, compared to map[:key]
, that would return nil
.
as well as:
The two syntaxes for accessing keys reveal the dual nature of maps. The map[key]
syntax is used for dynamically created maps that may have any key, of any type. map.key
is used with maps that hold a predetermined set of atoms keys, which are expected to always be present. Structs, defined via defstruct/1
, are one example of such “static maps”, where the keys can also be checked during compile time.
So should I read the above to always use map[key]
for dynamic maps even if the key is an atom, and map.key
only for static maps like a struct?