Difference between `{:a}` and `{:{}, [], [:a]}` in AST

iex(1)> value = {:a}
{:a}

iex(2)> a1 = quote do: a = {:a}
{:=, [], [{:a, [if_undefined: :apply], Elixir}, {:{}, [], [:a]}]}

iex(3)> a2 = quote do: a = unquote(value)
{:=, [], [{:a, [if_undefined: :apply], Elixir}, {:a}]}

iex(4)> a3 = quote do: a = unquote(Macro.escape(value))
{:=, [], [{:a, [if_undefined: :apply], Elixir}, {:{}, [], [:a]}]}

iex(5)> Macro.to_string(a1) == Macro.to_string(a2)
true

iex(6)> Macro.to_string(a2) == Macro.to_string(a3)
true

iex(7)> Macro.to_string(a1) == Macro.to_string(a3)
true

So what’s the difference between {:a} and {:{}, [], [:a]}? Does Elixir just expand {:a} inside the a2 AST to {:{}, [], [:a]}?

There are few things in Elixir that have 2 valid representations in AST:

  • 0-ary, 1-ary, and 2-ary tuples
  • <<"foo">> is treated the same as "foo"

The difference is that while Elixir will allow {:a} as an proper AST, it will never produce one. So {:{}, [], [:a]} is canonical representation and {:a} is accidental one.

4 Likes