Using Bindings to Inject Values

Remember that there are two ways of injecting values into quoted blocks. One is unquote. The other is to use a binding. However, the two have different uses and different semantics.

A binding is simply a keyword list of variable names and their values. When we pass a binding to quote, the variables are set inside the body of that quote.

This is useful because macros are executed at compile time. This means they don’t have access to values that are calculated at runtime.

Here’s an example. The intent is to have a macro that defines a function that returns its own name:

 defmacro​ mydef(name) ​do
 quote​ ​do
 def​ ​unquote​(name)(), ​do​: ​unquote​(name)
 end
 end

We try this out using something like mydef(:some_name). Sure enough, that defines a function that, when called, returns :some_name.

Buoyed by our success, we try something more ambitious:

macros/macro_no_binding.exs
 defmodule​ My ​do
 defmacro​ mydef(name) ​do
 quote​ ​do
 def​ ​unquote​(name)(), ​do​: ​unquote​(name)
 end
 end
 end
 
 defmodule​ Test ​do
 require​ My
  [ ​:fred​, ​:bert​ ] |> Enum.each(&My.mydef(&1))
 end
 
 IO.puts Test.fred

And we’re rewarded with this:

 macro_no_binding.exs:12: invalid syntax in def _@1()

At the time the macro is called, the each loop hasn’t yet executed, so we have no valid name to pass it. This is where bindings come in:

macros/macro_binding.exs
 defmodule​ My ​do
 defmacro​ mydef(name) ​do
 quote​ ​bind_quoted:​ [​name:​ name] ​do
 def​ ​unquote​(name)(), ​do​: ​unquote​(name)
 end
 end
 end
 
 defmodule​ Test ​do
 require​ My
  [ ​:fred​, ​:bert​ ] |> Enum.each(&My.mydef(&1))
 end
 
 IO.puts Test.fred ​#=> fred

Two things happen here. First, the binding makes the current value of name available inside the body of the quoted block. Second, the presence of the bind_quoted: option automatically defers the execution of the unquote calls in the body. This way, the methods are defined at runtime.

As its name implies, bind_quoted takes a quoted code fragment. Simple things such as tuples are the same as normal and quoted code, but for most values you probably want to quote them or use Macro.escape to ensure that your code fragment will be interpreted correctly.