In part 27 We learned that quote do…end turns your code into an Abstract Syntax Tree (AST), which is a data representation of your code, making it possible to modify the code at runtime. Next, we'll look at unquote/1 function, which lets you render a code implementation or value for the lacking a better term, into specific locations in your code during runtime.

While quote do…end makes code (often functions) portable by turning it into AST data, unquote allows you to extract the code's value and render it in a specific location within your codebase. quote do…end enables you to write portable code that you can inject anywhere in your program during runtime, while unquote works like #{string_variable_name}, allowing you to decide and precisely inject that code where you want.

Let's look at an example. We'll write ok/1 and noreply/1 functions to use in our LiveView.

defmodule SocialFund.Helpers do
  # We'll look at Marcos in the next article. For now, all you need
  # to understand is that it allows to do: `use ModuleName`

  defmacro __using__(_ops) do
    # Everything inside here will be injected in the module
    # like `use SocialFund.Helpers` but also if we want
    # `use SocialFund.Helpers, :live_view`  to inject
    # `ok/1` and `noreply/1` in the module
    quote do
      # We can o more here for example
      # use Phoenix.Router, helpers: false
      # import Plug.Conn
      # import Phoenix.Controller
      # import Phoenix.LiveView.Router
      def ok(socket), do: {:ok, socket}
      def noreply(socket), do: {:noreply, socket}
    end
  end
end

With the code above, we've made ok/1 and noreply/1 portable. They can be injected into any other module, and they'll behave as if they were local functions. Let's add these two functions to the PageLive module.defmodule

SocialFund.PageLive do
  use SocialFund.Helpers
  
  # The rest of module implementations...
end

Let's check if the two functions have been injected into PageLive. Open your application's interactive Elixir with iex -S mix and run the code below to list all functions defined in PageLive.

iex(1)> SocialFund.PageLive.__info__(:functions)
[noreply: 1, ok: 1]
iex(2)>

As you can see, the two functions have been added as if they were defined in the PageLive module. Let's try calling each one to confirm they produce the expected results. If you are coming from PHP world, this is almost like Traits in PHP.

iex(2)> SocialFund.PageLive.noreply("Test Noreply Injected Function")
{:noreply, "Test Noreply Injected Function"}
iex(3)> SocialFund.PageLive.ok("Testing quote again")
{:ok, "Testing quote again"}

Now, we've made ok/1 and noreply/1 portable, so we can reuse them in different modules, where they'll behave as if they were natively written in those modules. You might reasonably ask why we prefer this approach over import. While import only injects functions, use allows us to inject import, alias, require, and functions. For example, take a look at this code from your Phoenix web module:


  defp html_helpers do
    quote do
      # Translation
      use Gettext, backend: SocialFundWeb.Gettext

      # HTML escaping functionality
      import Phoenix.HTML
      # Core UI components
      import SocialFundWeb.CoreComponents

      # Common modules used in templates
      alias Phoenix.LiveView.JS
      alias SocialFundWeb.Layouts

      # Routes generation with the ~p sigil
      unquote(verified_routes())
    end
  end

Now that we understand what quote do…end does, let's explore unquote.

Understanding `unquote` in Elixir Meta Programming

unquote allows you to retrieve the value of a function or variable, and by using quote do…end, you can inject that value or function implementation into a specific slot in your code. This happens at compilation time, which is how we make code portable.

Let's look at an example by injecting the display_1 definition into the display function and observing it run as if the implementation were done in display. Just as you can render a string value anywhere using #{variable_name}, you can render a variable's value or a function's implementation anywhere using unquote.

Since the code is rendered in the Abstract Syntax Tree (AST), you must use quote do…end to create space in the AST and render the value in that specific spot using unquote.

defmodule SocialFund.PageLive do
  use SocialFund.Helpers

  def display_1 do
    # The value of the function or a variable we can
    # inject this value into anotheer function
    # by using quote to access AST and
    # using unquote to render it not
    # its function name
    "The value of the display"
  end

  def display do
    # Use Quote to modify the AST
    quote do
      # We are now inside the AST, we can modify it and inject code
      # Get the AST value of the display function 1 and inject
      # it at this particulate time. The unquote is the
      # equivalend of #{variable} to the string
      unquote(display_1())
    end
  end
end

You can confirm this by calling display in iex, and you'll see it returns the same results as display_1 because the implementation, value, and content of display_1 have been unquoted in display.

iex(43)> SocialFund.PageLive.display
"Text in display 1"

If you open your web module, in our case lib/helpcenter_web.ex, you'll notice extensive use of quote and unquote.

Once you understand how quote do…end and unquote work, you've grasped the fundamentals of editing code as data at runtime or metaprogramming, and you're ready for the next step. We'll explore defining our own domain-specific language (DSL) using defmacro, with a practical example of how Ash builds its declarative syntax.