Why is the syntax tree for a 2-tuple different than for other tuples?

Because Elixir code is parsed into 3-tuples, there needs to be a different notation for an embedded 3-tuple. It is what I think of as “constructor” notation:

iex(12)> quote do: {1, 2, 3}
{:{}, [], [1, 2, 3]}

That’s true for other tuple lengths, which makes sense: why not be consistent?

Except for 2-tuples, which are represented unchanged:

iex(7)> quote do: {1, 2}
{1, 2}

My hunch is that’s because lists are unchanged:

iex(8)> quote do: [1, 2, 3]
[1, 2, 3]

… and making 2-tuples unchanged means that keyword lists are also unchanged:

iex(11)> quote do: [a: 1, b: 2]         
[a: 1, b: 2]

I can’t think of a case where treating 2-tuples like other tuples would cause ambiguity, though. Is this just to make literal keyword lists more readable?

Maps also have a constructor representation:

iex(13)> quote do: %{a: 1}
{:%{}, [], [a: 1]}

Why is that needed?

It’s because the Elixir AST is optimized for developer ergonomics when writing macros, it’s not a goal to be regular(in which case it would be more verbose).

Having them as 2-tuples, in combination with lists being literals in the AST, is what makes it easier to write macros for keyword blocks, like

defmacro schema(table, do: fields) do ...

If the AST were regular, you’d need to do something like this

defmacro schema(table, [{:{}, _, [:do, fields]}]) do ...

And you can imagine how it would be more complicated for other macros. Processing the keyword syntax for Ecto queries would be much more verbose, for example.


Thanks. Very clear. Though I still wonder about maps.

FWIW, Elixir properly handles 2-element tuples in uniform AST notation, for the sake of convenience.

defmodule Tuple2 do
  defmacro t(list), do: {:{}, [], list}

require Tuple2
Tuple2.t([:a, :b])
#⇒ {:a, :b}