Understanding Elixir's Conversion of Blocks into Keyword Lists

In the context of iex…

…yields no error

IO.inspect :a do
5
end
#==> :a

…yields an error

:a do
5
end

error: syntax error before do

This question is in regards to the treatment of blocks in elixir, as I am trying to understand when they are automatically converted into kwlists.

They’re always converted to kw lists in contexts that allow kw lists. You can’t pass a kw list to the atom :a – it’s a syntax error.

For perhaps more clarity, the first example is equivalent to both of the following:

IO.inspect(:a, do: 5)
IO.inspect(:a, [{:do, 5}])

The :do is being passed as an option to the inspect function (and then ignored).

2 Likes

That cleared up everything, especially the added clarification. Which explains why the block was ignored (not outputted) by the inspect.

Just to clarify, the macro tag is because I wanted to know if this conversion takes place even when internal representation is taking precedent (such as when passed as an arg to a macro), but this seems to happen before all that.

1 Like

Here’s a handy guide on syntax sugar: Optional syntax sheet — Elixir v1.16.2

It’s definitely worth understanding. There’s surprisingly little of it!

4 Likes

Thanks, was looking for something like this, but was searching under “blocks”.

More appreciation for the way the core team achieved elegance in elixir’s syntax.

1 Like

It also might be useful to think of it in “reverse” as it’s a little more accurate: The do syntax is actually sugar. It’s really more: “when can do stand in for a :do key in a kwlist” if that makes sense. The “truer” syntax—for lack of a better word—is def foo, do: "foo", or rather def foo, [{:do, "foo"}].

I actually have a bit of trouble myself grokking how the def family of macros work because otherwise Elixir is really very lispish—it’s just keyword lists all the way down.

defmodule(Foo, [{:do, def(bar, [{:do, "foo"}])}])
1 Like

One thing I didn’t understand in the example from the guide was

defmodule Math do
  def add(a, b) do
    a + b
  end
end

turns into

defmodule(Math, [
  {:do, def(add(a, b), [{:do, a + b}])}
])

What happens with add(a, b), but since you said def is a macro this would be because add(a, b) does not get evaluated, but rather literally inserted into some internal representation code fragment?

Yes, but I never fully understood how they work the parens.

If you really wanna bend your brain, see here.

Here be dragons.

This can’t be the initial definition though?

Is this due to elixir being homoiconic?

I honestly don’t know! I haven’t never taken the time to fully understand at this level. defmacro defmacro calls define(:defmacro, ...) so AFAIK it’s the initial definition, at least in the Elixir part of Elixir’s source. Clearly there is something in the Erlang bits that are making that possible.

1 Like

Ah just as I thought. Seems like macros are actually just quotes and unquotes but with some default behaviour with respect to having at least on unquote at the end of the macro.

I don’t know the details but, I just cursory view at defp define, and it seems like quote and unquote may be closer to the Erlang side of the implementation than defmacro itself.

Thanks for your answers.