Elixir: Dot-notated atoms cannot be used as keys in keyword lists (or maps).

Created on 16 Apr 2020  路  5Comments  路  Source: elixir-lang/elixir

Precheck

  • For bugs, do a quick search and make sure the bug has not yet been reported

    • I scanned the issues and did not see a report of this or a similar bug. Issue #4561 is similar, but does not quite address the same problem
  • Finally, be nice and have fun!

    • I 馃挏 Elixir

Environment

  • Elixir 1.8.1 (Compiled with Erlang/OTP 22)
  • Elixir 1.10.2 (Compiled with Erlang/OTP 22)
  • macOS Mojave 10.14.6

I ran the following examples in my project's version of Elixir (1.8.1), as well as the latest version.

Current behavior

Atoms that use dot-notation, like module names, cannot be used as keys in keyword lists (or maps).

iex(1)> Keyword.keyword? Hello.There: :world
** (SyntaxError) iex:1: syntax error before: 'There'

iex(1)> 

As you can see, it appears Hello.There is an invalid key, resulting in a syntax error.
Lets check out the definition of Keyword.keyword?/1 in Elixir source code.

def keyword?([{key, _value} | rest]) when is_atom(key), do: keyword?(rest)
def keyword?([]), do: true
def keyword?(_other), do: false

Cool, so if a key can evaluate to true when passed into is_atom/1, it should be a valid key right?

iex(1)> is_atom(Hello.There)
true
iex(2)> 

Hmm... wasn't expecting this. A step further: how is is_atom/1 defined?

def is_atom(term) do
  :erlang.is_atom(term)
end

So lets try invoking the Erlang function directly to reproduce that syntax error

iex(1)> :erlang.is_atom(Hello.There)
true
iex(2)> 

Hmm... ok. What is going on here? Lets try creating a keyword list without the syntactic sugar.

iex(1)> Keyword.keyword? [{Hello.There, :world}] 
true
iex(2)> 

That ... works? Ok so there appears to be something going on with how Elixir evaluates keyword lists that employ the syntactic sugaring vs. those that use the full syntax.

Another thing I noticed is that quoted atoms are only equivalent to their unquoted counterparts if the atom does not resemble a module name.

iex(1)> :hello == :"hello"
warning: found quoted atom "hello" but the quotes are not required. Atoms made exclusively of Unicode letters, numbers, underscore, and @ do not require quotes
  iex:1

true
iex(2)> Hello.There == :"Hello.There"
false
iex(3)> 

Not sure if this is related to the same issue, but interesting nonetheless.

Expected behavior

According to Elixir docs and Programming Elixir, module names are just atoms internally. Therefore, I expected to be able to use a module name as key in a keyword list (or map).

Forgive me for my naivet茅 if I am overlooking something obvious. And thanks to the Elixir team and community for everything you do 馃檹

All 5 comments

Hi @parkerduckworth. Although aliases compile down to atoms, they are a different syntax construct from atoms. You can see this in the AST:

iex(1)> quote do: Hello.There
{:__aliases__, [alias: false], [:Hello, :There]}
iex(2)> :hello
:hello

For example, this is not supported either: :Hello.There. In your case, you need to write: :"Hello.There" or ["Hello.There": :foo]. But note that :"Hello.There" != Hello.There. Again, they do compile to atoms, but they are ultimately different in the syntactical representation.

Ah, I see.

iex(1)> quote do: [{Hello.There, :world}]
[{{:__aliases__, [alias: false], [:Hello, :There]}, :world}]
iex(2)> kw = [{Hello.There, :world}]
[{Hello.There, :world}]
iex(3)> Keyword.get(kw, Hello.There)
:world
iex(4)> 

I guess my question is, why does omitting the syntactic sugaring from a keyword list allow us to use aliases as keys @josevalim ?

I guess my question is, why does omitting the syntactic sugaring from a keyword list allow us to use aliases as keys @josevalim ?

Because they are still atoms. That's a runtime behaviour. :) The keyword syntax is restricted to mirror the atoms syntax. It could work as you propose too, but if you assume you can only put : after things, then folks may expect [1: 2] to work too. Or [{1, 2}: :bar] and so on. And perhaps, if it is about putting the : after, maybe it should have been [:foo: :bar]? :)

Understood. Thanks for the explanation!

iex(1)> Hello.There == :"Hello.There"
false
iex(2)> Hello.There == :"Elixir.Hello.There"
true

I hope this clarifies it

Was this page helpful?
0 / 5 - 0 ratings

Related issues

cmeiklejohn picture cmeiklejohn  路  3Comments

ericmj picture ericmj  路  3Comments

andrewcottage picture andrewcottage  路  3Comments

chulkilee picture chulkilee  路  3Comments

GianFF picture GianFF  路  3Comments