Reputation: 66
I'm learning control structures from https://elixirschool.com/en/lessons/basics/control-structures/ and I noticed that it mentions
Chances are you’ve encountered if/2 before, and if you’ve used Ruby you’re familiar with unless/2. In Elixir they work much the same way but they are defined as macros, not language constructs. You can find their implementation in the Kernel module.
so what's the difference between a language construct and a macro in Elixir and when is it necessary to write a macro?
Upvotes: 0
Views: 176
Reputation: 2922
A macro is a way to program a programming language. Simply put, a macro is a way to generate program code, instead of writing it yourself all the time.
A language construct on the other hand (sometimes called "special form"), is something that is at the core of elixir itself. An oversimplification could be that the implementation of if
is not done in Elixir, but in the language in which Elixir is implemented.
Suppose you want to use the mentioned unless
.
Edit: unless
is available in Elixir. But let's assume for the remainder that it is not.
In Elixir, there is no unless
available in the language. José Valim did not implement it. But you can always write something that has the same semantics: a negated if
.
We would like to have this, but we don't:
unless sun_shines() do
open_umbrella()
end
But we only have an if
and a not
, so we can write:
if not sun_shines() do
open_umbrella()
end
Secondly, a macro is a special kind of function, but its parameters are code, and the result of executing a macro is code as well. Assuming we have the unless
macro, it takes in a condition (i.e., sun_shines()
), and a body (i.e., open_umbrella()
), and returns if not sun_shines(), do: open_umbrella()
. So a macro is a function that works at the level of your "dead code" and generates "dead code".
You might think that this is just too stupid to write a macro for. That's true. But these types of problems happen more often than you think, and then a macro is a great solution to that problem. It's just a way to program your programming language.
An example implementation of the unless
macro has been provided by Aleksei Matiushkin:
defmodule MyMacros do
defmacro unless(ast, do: block) do
quote do
if not unquote(ast) do
unquote(block)
end
end
end
end
Here you can clearly see that you give it an AST (Abstract Syntax Tree), and it will transform it to another AST (quote
), and inject that in the place where you called the macro. Note that this all happens at compile time. Your program is not being executed at this point!
For example, suppose you have the above module available, and this is your program:
defmodule MyProgram do
def my_function(x) do
unless sun_shining() do
open_umbrella()
end
end
end
After compilation, and before execution, your program will look like this:
defmodule MyProgram do
def my_function(x) do
if not sun_shining() do
open_umbrella()
end
end
end
This phase is what we call macro expansion phase.
As an extra, here you can find two actual macros used in Elixir and ExUnit respectively.
Note: I keep adding more and more information to this answer. The answer actually deserves a whole book and Metaprogramming Elixir by Chris McCord is the best one.
Upvotes: 3