laiboonh

laiboonh

Can someone enlighten me on this piece of code?

This is a code snippet from the metaprogramming elixir book.

defmacro test(description, do: test_block) do
    test_func = String.to_atom(description)
    quote do
      @tests {unquote(test_func), unquote(description)}
      def unquote(test_func)(), do: unquote(test_block)
    end
  end

Can someone enlighten me on what is def unquote(test_func)(), do: unquote(test_block) doing? How is it even legal syntax??? I tried it out on iex:

iex(67)> defmodule Test do
...(67)> def test(a)(), do: a
...(67)> end
** (CompileError) iex:68: invalid syntax in def test(a)()
    iex:68: (module)

Most Liked

h4cc

h4cc

unquote is a function to “create code from data” simply said.

When this is executed
def unquote(test_func)(), do: unquote(test_block)

the result will be
def :description_as_atom(), do: [list, of, test, blocks]
which will be valid code for the compiler.

Short: unquote will mostly be called in macros to generate code from parameters (which is data at compile time), so only the result of unquote will stay afterwards.

OvermindDL1

OvermindDL1

Eh, not really like assembler, it is the very definition of AST. Elixir’s AST. :slight_smile:

The compilation process goes: Elixir → Elixir’s AST (what quote gives you) → Erlang (Abstract Format, basically it’s AST) → Core Erlang → BEAM
With a variety of translators along the way too. :slight_smile:

Basically take this Elixir:

defmodule :tester do

  def hi, do: "there"

end

To this Elixir AST:

{:defmodule, [context: Elixir, import: Kernel],
 [:tester,
  [do: {:def, [context: Elixir, import: Kernel],
    [{:hi, [context: Elixir], Elixir}, [do: "there"]]}]]}

To this Erlang:

-module(tester).
-export([hi/0]).
hi() -> <<"there">>.

To this Core Erlang:

module 'tester' ['hi'/0,
                 'module_info'/0,
                 'module_info'/1]
    attributes []
'hi'/0 =
    %% Line 3
    fun () ->
        #{#<116>(8,1,'integer',['unsigned'|['big']]),
          #<104>(8,1,'integer',['unsigned'|['big']]),
          #<101>(8,1,'integer',['unsigned'|['big']]),
          #<114>(8,1,'integer',['unsigned'|['big']]),
          #<101>(8,1,'integer',['unsigned'|['big']])}#
'module_info'/0 =
    fun () ->
        call 'erlang':'get_module_info'
            ('tester')
'module_info'/1 =
    fun (_cor0) ->
        call 'erlang':'get_module_info'
            ('tester', _cor0)

To this BEAM Assembly:

00007F031584F908: i_func_info_IaaI 0 tester hi 0
00007F031584F930: move_return_c <<"there">>

00007F031584F940: i_func_info_IaaI 0 tester module_info 0
00007F031584F968: move_cr tester r(0)
00007F031584F978: allocate_tt 0 1
00007F031584F988: call_bif_e erlang:get_module_info/1
00007F031584F998: deallocate_return_Q 0

00007F031584F9A8: i_func_info_IaaI 0 tester module_info 1
00007F031584F9D0: move_rx r(0) x(1)
00007F031584F9E0: move_cr tester r(0)
00007F031584F9F0: allocate_tt 0 2
00007F031584FA00: call_bif_e erlang:get_module_info/2
00007F031584FA10: deallocate_return_Q 0

And that is what is loaded by the VM.

Eiji

Eiji

@laiboonh: Inside quote do_block your expressions are quoted.
For example:

defmodule Example do
  defmacro sample do
    IO.inspect quote do: a
    :ok
  end
end
require Example
Example.sample
# {:a, [], Example}

Similarly test_func and description are quoted too.
Simple explanation for new developer is that unquote converts quoted expressions (AST) to their “normal” form.
For example:

defmodule Example do
  defmacro sample(a) do
    quote do
      unquote(a)
    end
  end
end
require Example
Example.sample(5)
# returns 5 instead of: {:a, [], Example}

So your line is “translated” from Elixir AST:

def {:test_func, [], ModuleName}(), do: {:block, [], ModuleName}

to “normal” code like:

def test_func(), do: test_block

You can see how it looks by inspecting variables in quote do_block without unquote part.
Please see: Quote and unquote for more informations.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Think of unquote as similar to #{} in strings. If you have name = "Bob"; greeting = "hello #{name}" the contents of name are injected into the greeting string producing "hello Bob".

When you do something like

capitalize_ast = quote do: String.capitalize("hello")
result = quote do: IO.inspect(unquote(capitalize_ast))

you’re inserting the AST contents of capitalize_ast into result which makes result the AST: IO.inspect(unquote(String.capitalize("hello"))

Where Next?

Popular in Questions Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
dotdotdotPaul
Okay, I'm having a heck of a time trying to figure out how to best handle the validation of belongs_to associations in Ecto. I'm sure I'...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
yawaramin
In the Dialyzer docs ( http://erlang.org/doc/man/dialyzer.html#requesting-or-suppressing-warnings-in-source-files ), there is a way to tu...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
beno
I will often find my self writing things similar to: case some_value do nil -&gt; something() "" -&gt; something() _ -&gt; someth...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

peerreynders
Manning 2016 Halloween weekend sale via Deal of the Day Friday, October 28 - Half off all MEAPs - code WM102816LT Saturday, October 29 ...
326 29600 154
New
sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
AstonJ
You’re a programmer, so you don’t need spoon feeding with the conventional drivel about “this is an integer.” No. You need to know what’s...
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
shahryarjb
Hello, I have map which I want to convert it to string like this: the map: %{last_name: "tavakkoli", name: "shahryar"} the string I ne...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
vrod
I am using the Starship cross-shell prompt – it seems pretty nice, but I get some errors: [WARN] - (starship::utils): Executing command ...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

We're in Beta

About us Mission Statement