augnustin

augnustin

I don't understand quote/unquote: why do we need them?

Hi,

I have been coding with Elixir for 9+ months and I encountered several time the concept of quotes and each time, I think “ok, today I shall be mature enough to understand quotes”, re-read the content I can find on the internet (starting with the getting started).

But until now, I still haven’t understood the blood out of it. :frowning:

My main question is why do we need them?

If quoting is only about changing the representation of a piece of code, then why should it be so different quoted than unquoted. I’m comfortable with the concept of AST, but as for me it only changes the representation of something, not its content.

Eg. 1 + (2 + 3) <==> add(1, add(2, 3))

Besides, it feels illogical to me that there are two worlds: the quoted one and the unquoted. If quoting is about meta-programming, then quoting quoted expressions should make sense to program meta-meta-programming, shouldn’t it?

Someone in this forum suggested that macros (that are closely related to quotes, aren’t they?) should be seen as functions that return other functions instead of expressions. This helped me a little to read some quote-using-code, but it still doesn’t explain why we need them.

I am comfortable with currying, partial application, function as references, which are the way meta-programming goes in languages I’m more comfortable with (JS, Haskell …), so what’s different here?

Finally, are quotes so hard to understand or is it just me?

Of course, I feel like this is key concept to be comfortable with Elixir, I can be patient with that, just like I was (am?) with Haskell’s monads, but nothing out there suggests that this one has to expect something hard so it may be just me…

Thanks for the help, hope this can be useful to others!

Marked As Solved

LostKobrakai

LostKobrakai

There are two worlds because there are two times when code is executed:

  • At compile time (any code, which is not a function body is executed + macros)
  • At runtime (Function bodies are executed)

Take this example:

defmodule A do
  for {i, level} <- %{1 => :err, 2 => :warn, 3 => :info} do
    def log_level(i), do: level
  end
end

For the function log_level what do you expect i and level to represent?
Basically it’s 3 times this: def log_level(i), do: level, and it would complain about unmatchable clauses (all match i) as well as crash at runtime for an undefined variable level.

This is where unquoting comes in.

defmodule A do
  for {i, level} <- %{1 => :err, 2 => :warn, 3 => :info} do
    def log_level(unquote(i)), do: unquote(level)
  end
end

At compile time this will define a module A with 3 function heads looking like that:

def log_level(1), do: :err
def log_level(2), do: :warn
def log_level(3), do: :info

The runtime will never know of i and level and therefore not fail because of undefined variables. The same thing happens in macros, where you also create AST at compile time. Macros can however also edit AST.

You might be wondering why this example doesn’t use a quote like macros do. For a module it’s clear which code is compile time executed and which isn’t (only function bodies aren’t executed at compile time), so all the quote/unquote stuff happens as part of the defmodule implementation. For macros there needs to be a distinction between code executed at compile time and the code it actually generates. To not have you manually edit AST nodes there’s quote. It takes its body and converts the contents into AST. It’s basically a AST generator. quote has the same problem as I explained above for the module body, therefore it uses unquote as well, to put data you have at compile time “hard coded” into the generated AST.

The complexity really hits if you want to create the module body from my example (those unquotes are usually named unquote fragments) within the quote of a macro. This is where you need to use the bind_quoted option for quote to remove the ambiguity.

43
Post #4

Also Liked

NobbZ

NobbZ

Quote and unquote are the AST equivalent of the double quote that encloses a string and #{} which you use in a string to inject a variables value rather than just it’s name.

They are essential for code generation, meta programming and macros

entone

entone

Let’s assume we have a function that is going to generate a function for us, and we want to pass in some arguments that can get pattern matched on.

def create_handle_event_function(event, next) do
  quote do
    def handle_event(unquote(event) = runtime_event, state) do
      state = handle_our_event(runtime_event, state)
      {:ok, state, unquote(next)}
    end
  end
end

This allows us to pass in compile time arguments, that can get evaluated/pattern matched at runtime.

So unquote allows us to pass in compile-time args, and get the AST of the value for runtime.

Not sure if that helps at all.

augnustin

augnustin

This is amazing. 1 year later I come back here and now it feels like macros are something I could get comfortable with. :fireworks:

Thanks @LostKobrakai your example really helps.

Where Next?

Popular in Questions Top

New
Werner
Hi, I’m using Ubuntu 18.04 and after updating to OTP-24.0 yesterday i have this warning when I run “mix local.hex”: 14:57:30.512 [warn] ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
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
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
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
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New

Other popular topics 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
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
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

We're in Beta

About us Mission Statement