简体   繁体   中英

Language constructs vs Macros in elixir

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?

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 (ie, sun_shines() ), and a body (ie, 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.

https://github.com/elixir-lang/elixir/blob/d48b16cf549eca0629449a47cc5574a7170706c3/lib/ex_unit/lib/ex_unit/assertions.ex#L104

https://github.com/elixir-lang/elixir/blob/13ced80fcda1bea69037aacd4b052a0c44b4be61/lib/elixir/lib/stream/reducers.ex#L58

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM