What's wrong with: is_number if true do 1 end

Viewed 108

Why is the second form parsed as 2 args being passed to is_number?

is_number if true, do: 1
true

is_number if true do 1 end
** (CompileError) iex:42: undefined function is_number/2
1 Answers

We can use the quoting mechanism to see how an expression is being parsed in Elixir.

iex(6)> IO.puts Macro.to_string(quote do: (is_number if true, do: 1))
is_number(if(true) do
  1
end)
:ok
iex(7)> IO.puts Macro.to_string(quote do: (is_number if true do 1 end))
is_number(if(true)) do
  1
end
:ok

So in your first case, the do gets parsed as an argument to if, and the whole thing gets passed to is_number. In the second, the do has a different precedence and ends up binding to the is_number call itself as a second argument, so we end up calling if/1 and is_number/2, which is nonsense.

As for why this is, it's common to use do to capture full expressions as statements, like

if something_really_complicated arg1, arg2, arg3 do
  # ...
end

So, in this case, it makes sense to bind do as loosely as possible. Conversely, if we try to pass do: as a keyword argument with an explicit comma, then it's just an argument like any other, so it follows the usual argument passing rules rather than a special one.

The key thing to remember in a language like Elixir is that many things that are keywords in other languages really aren't in Elixir. if statements parse exactly the same way macros would, and you can write your own if macro easily, and do: is really just a keyword argument that has a bit of syntax sugar attached to it, not anything specific to any particular control statement. This is a big part of what makes Elixir (and company) excellent for domain-specific tasks.

Related