Key value syntax with "key: :value"

I’m having trouble understanding why on the one hand the following is true and valid syntax
[{:one, 1}, {:two, 2}] == [one: 1, two: 2]
and on the other hand simply putting one: 1 seems not to be valid syntax in elixir (shell).
I would have expected
{:one, 1} == one: 1
But this seems not to be true. Why is this?
Furthermore quote(do: :something) is apparently evaluating to quote([{:do, :something}]). Here I would have expected that the valid expression would have been quote({:do, :something}), which results, however, in an error.

Does someone have more insight why my intuition is wrong?

1 Like

[one: 1, two: 2] is a keyword list, and in reality it is syntactic sugar for [{:one, 1}, {:two, 2}]. That is why they are equal to each other.

{:one, 1} outside of a list is just a tuple, but if you put it inside a list it will become a keyword list; in short: a keyword list is a list of two-tuples where the first element is an atom. Outside the list one: 1 will be a syntax error…but!

If the keyword list is the last argument to a function you can omit the brackets for the list. This again is syntactic sugar allowing you to do stuff like:

GenMQTT.start_link(__MODULE__, %{}, username: "martin", password: "secr3t")

Which, besides being a bit magical, is deemed readable—might take some time getting used to because we all of a sudden have a function that takes 3 arguments and seemingly have 4…but bear with me here.

This is the reason quote(do: :something) will return the representation of a list with a two-tuple—a keyword list. It is the last argument given to the quote/1 function.

5 Likes

Thanks. I understand much better now. The possibility to omit the brackets in the last function argument is crucial to notice.

I would like to add to your excellent response that the “keyword syntax” is allowed as the “last argument” throughout Elixir. If you remember that %{}, {}, etc are all also “function calls” in Elixir’s AST, if we take this call:

any_function(:one, :two, three: 3, four: 4)

which is equivalent to this call:

any_function(:one, :two, [three: 3, four: 4])

We can also take this call:

{:one, :two, three: 3, four: 4}

and it will be equivalent to:

{:one, :two, [three: 3, four: 4]}
7 Likes

FWIW, it took me a long time to figure out what was really going on with this when I first started learning Elixir. It was the only thing I really stumbled on since it was just that bit over the line between “magic” and convenient syntax for me.

1 Like