laiboonh

laiboonh

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)

Showing Posts 1 to 8

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.

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.

laiboonh

laiboonh OP

Hi, thanks for helping me out here. One thing i don’t understand is what actually happens in unquote. I have modified your code slightly

defmodule Example do
  defmacro sample(a) do
    IO.inspect quote do: unquote(a)
    :ok
  end
end

Let’s not use AST literals but something else so that we can see the difference. Example.sample(%{name: "Lai"}). This is my understanding, correct me if i’m wrong.

I understand that arguments become quoted expression inside the body of a macro. Hence a becomes {:%{}, [line: 41], [name: "Lai"]} inside the macro body.

unquote is like string interpolation hence we “substitute” in a and the macro body becomes IO.inspect quote do: {:%{}, [line: 41], [name: "Lai"]}

finally when you quote some quoted expression (AST) you get back the same quoted expression and IO.inspect prints out {:%{}, [line: 41], [name: "Lai"]}

Is this how it goes? I am a bit confused by your statement.

  1. Was there any conversion going on or simply a substitution like i said.
  2. What is “normal” form? is it from my example {:%{}, [line: 41], [name: "Lai"]} or %{name: "Lai"}
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"))

laiboonh

laiboonh OP

Actually i was just curious whether unquote does a substitution or does a conversion as well like Eiji mentioned. I rationalized that its like you said a mere substitution.

iex> x= quote do: %{name: "Lai"}
iex> (quote do: IO.inspect(unquote(x))) |> Macro.to_string
"IO.inspect(%{name: \"Lai\"})"

iex> x = %{name: "Lai"}
%{name: "Lai"}
iex(130)> (quote do: IO.inspect(unquote(x))) |> Macro.to_string
"IO.inspect(%{name: \"Lai\"})"

No matter unquote is unquoting an expression or a quoted expression, it will in the end result in an AST because unquote has to happen within a quote block

Eiji

Eiji

@laiboonh: You can think about AST like a Assembler code.
AST is a data (in tuple notation), but it could be nested instead of simple Assembler plain instructions list.
quote is changing code to AST, so you are generating quoted code in quote block. You cannot access variables outside of this block unless you call unquote.
You can think about quoting like about generating file using controller data. Your controller data are all variables inside your macro/function including it’s arguments. To access them instead of calling <%= assigns.something %> you are using unquote(something).
unquote fetches raw value of variable and/or expression and puts it into quote block.
Finally quote will return quoted code block and Elixir compiler will generate “normal” code (normal - I mean that code you can see - without AST) - it’s expanding instructions to real code.

About your code:

  1. You are unquoting quoted expression (here expression is just a map).
  2. You are unquoting raw expression (again - here it’s just map).

both of them returns unquoted raw value of that expression.

What unquote is doing is like undo of one quote call and evaluate it putting into quoted expression.
When you are working for example with lists in recursive method like:

def do_something([head | tail]), do: to_string(head) <> do_something(tail)

you need to implement also case when do_something is called with empty list like:

def do_something([]), do: ""

Similarly unquote works with raw values. You have quoted variable/expression n times. unquote is doing undo for first (top) quote, but when it’s called with raw value then it just returns that value and finally in both cases it evaluates it.
So:

first = quote do: unquote 5
second = quote do: unquote quote do: 5
first == second # true
# but:
third = quote do: unquote quote do: quote do: 5
first != third # true
# more:
fourth = quote do: 5
first == fourth
fifth = quote do: quote do: 5
third == fifth

Note: I used here raw value (5) that’s already unquoted.

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.

laiboonh

laiboonh OP

Nice code there to challenge one’s understanding of unquote but i think its more illustrative if we used something that is not an AST Literal

#From the inside out, quote do: {1,2,3} returns {:{}, [], [1, 2, 3]}, unquote just interpolates 
#this inside the AST (outer quote block) hence we end up with {:{}, [], [1, 2, 3]}
iex(13)> quote do: unquote quote do: {1,2,3}
{:{}, [], [1, 2, 3]}

#we interpolate {1,2,3} into the AST (quote block), this is an invalid quoted expression 
#by the way
iex(14)> quote do: unquote {1,2,3}
{1, 2, 3}

#we quote on a quote. quote is also a function call hence we end with with something like this
iex(15)> quote do: unquote quote do: quote do: {1,2,3}
{:quote, [], [[do: {:{}, [], [1, 2, 3]}]]}

#quote on an expression to get a quoted expression (of that expression)
iex(16)> quote do: {1,2,3}
{:{}, [], [1, 2, 3]}

#again we do a quote on quote
iex(17)> quote do: quote do: {1,2,3}
{:quote, [], [[do: {:{}, [], [1, 2, 3]}]]}
— All posts loaded —

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
peck
Hi everyone. My team and I have been working on a fairly modest app based around video streaming and chat, but we’ve landed a customer t...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
bradley
I really like the adapter patterns that ecto, nebulex, waffle, etc. use and would love find something similar for a key management servic...
New
unaware8150
Hello folks! So at work, we are seeing some situations where we have to define some “fixed” strings that are used across the codebase in...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
Alvinkariuki
How Can I Optimise Compile Time Dependencies I have been building an elixir application for about 2 years now. Many modules and files ha...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New
KristerV
Hey. Is there anyone here who creates agents in their apps? Not talking about using agents, but creating them. I’m finding it pretty diff...
New
mudasobwa
I fully migrated to my own harness from Anthropic/Gemini and I think it’s time to share it. Welcome DSH, the DeepSeek Harness, fully writ...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
juhalehtonen
There has been a thread to discuss the Stack Overflow Developer Survey on this forum every year since 2018, so here’s yet another one for...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews