Quote Do, Unquote, Use, Require, and Import?

Understanding code reuse in Elixir

I’ve been working mostly with Ruby and Rails since I started web development a few years ago and it’s been great, but I keep hearing about Elixir and Phoenix and how fantastic they are. I am finally getting the chance to dip my toe in, and I’ve found a couple of things to be a bit confusing right from the start. If you’ve been working in the Ruby/Rails land, you may have gotten tripped up understanding import, use, quote do as well. Let’s take a minute to go over how classes get their methods in Ruby and then look at the steps that I took to better understand the world of Elixir.

In Rails, whenever I’m confused about where an object gets its method, I feel like I have a pretty good handle on where to check. I first go to the file that defines the class. If it’s not there, I check and see if the class includes (instance methods) or extends (class methods) any modules. If I still can’t find it, I look for whether the class inherits from another and then check there. Outside of ActiveRecord or other meta programmed methods, this usually gets me where I’m going. Jumping into Elixir and Phoenix though, I started to get a little lost…

Just looking at a standard Phoenix controller, I see the following at the top:

defmodule MyAppWeb.CourseController do
use MyAppWebWeb, :controller

After hunting around and finding the my_app_web.ex file, I found this:

def controller do
quote do
use Phoenix.Controller, namespace: RegistrarWeb
import Plug.Conn
import RegistrarWeb.Router.Helpers
import RegistrarWeb.Gettext
end
end

import? use? quote? What are all the commands and where are my functions coming from‽

Import

import brings all (or some, depending on the syntax) of the functions in from another module. This can be used to easily access functions without having to call them from their parent module’s name. Let’s take a look at an example.

defmodule RouteHelper do
def dogs_route(%{id: dog_id}) do
"/dogs/#{dog_id}"
end
end
defmodule DogController do
def show do
dog = %{ id: 1, name: "Boo" }
IO.puts RouteHelper.dogs_route(dog)
end
end

If we were to run this, we’d get the following:

iex(1)> DogController.show()
/dogs/1
:ok

In the code snippets above, we’re calling the function that is stored in the RouteHelper like so:

Now every time we want to use dogs_route, we need to access it from the RouteHelper, but if we’re going to be using this a lot and maybe we want this function to be included in the DogController we can import RouteHelper at the top.

defmodule DogController do
import RouteHelper
  def show do
dog = %{ id: 1, name: "Boo" }
IO.puts dogs_route(dog)
end
end

Now we can run our code and get the same output:

iex(1)> DogController.show()
/dogs/1
:ok

It’s not recommended to use import too often because let’s say we were using many different modules in our DogController and we decided we would just use import for all of them. The first problem we may have is that we’re actually bloating our compiled code because when we compile we are actually making a copy of the functions from one module to another rather than just accessing the function from the parent module. The image below illustrates the difference in the compiled code. We can see that the dogs_route function now exists twice in the compiled code: once in the RouteHelper and now once again in the DogController.

Another reason to caution against overusing import is that we start to lose track of where each function is defined. Explicitly calling a function from a module makes it easier to track down the source and helps ensure that there is no naming conflict with functions of the same name living in different modules.

If you do decide to use import though, you may want to be as selective as possible to reduce bloat and be as clear as possible for where functions are coming from. You can use the import RouteHelper, only: [:dogs_route] syntax to specify only specific functions to import rather than an entire module.

Quote

Now quote do is a little different. It returns an abstract representation of a function (Abstract Syntax Tree) rather than the function itself. Let’s take a look to see how that is represented.

iex(1)> quote do
...(1)> def dogs_index() do
...(1)> "/dogs"
...(1)> end
...(1)> end
{:def, [context: Elixir, import: Kernel],
[{:dogs_index, [context: Elixir], []}, [do: "/dogs"]]}

This returns some nested tuples that we can pass around. We could never do this with a function in Elixir because a function must be defined in a module. If we try to just define a function in global space, look what happens:

iex(1)> def dogs_index() do
...(1)> "/dogs/index"
...(1)> end
** (ArgumentError) cannot invoke def/2 outside module
(elixir) lib/kernel.ex:5142: Kernel.assert_module_scope/3
(elixir) lib/kernel.ex:3905: Kernel.define/4
(elixir) expanding macro: Kernel.def/2
iex:1: (file)

The idea here is that we can define a dynamic function that will in turn write different functions for us depending on where/how it is implemented. Now let’s look at a small example where we may want to make a couple of helper functions for our controllers like dogs_index/0, dogs_show/1, cats_index/0, cats_show/1, turtles_index/0, turtles_index/1. Now if we want to have the resource name in the function name as we do here, we’ll have to use this abstract form of the function to define all these functions.

Now as far as writing your own quote blocks, I’d suggest to tread carefully as it can definitely increase the difficulty of reading your code. It can be great for implementing some boilerplate (like needing to import the same four modules into a bunch of other modules) or if you need some common behavior (like if you were building your own framework or want the same CRUD actions in multiple module). If you feel like you’re being clever when you’re using quote though, you may want to consider avoiding using it and optimizing for being explicit instead.

Require (and macros)

Again, I don’t want to get too deep into meta programing here, but a macro is used to define a function. A function that writes a function! How does a macro do it? By returning an abstract version of the function like we built above with quote. Let’s try it out.

defmodule ControllerHelper do
defmacro create_link_helper() do
quote do
def dogs_index do
"/dogs"
end
end
end
end
defmodule DogController do
import RouteHelper
require ControllerHelper
ControllerHelper.create_link_helper()
  def show do
dog = %{ id: 1, name: "Boo" }
IO.puts dogs_route(dog)
end
end

Now you’ll notice we had to actually require ControllerHelper here at the top of the DogController. We can usually just call a function from another module, but when using macros, we need to let Elixir know what specific files need to be loaded up before compiling this file so that it can properly build the macro. That’s why it’s not enough to just have the ControllerHelper.create_link_helper(), but we have to require the module beforehand.

Now if we get the following:

iex(1)> DogController.dogs_index()
"/dogs"

Great… but that hasn’t really gotten us anywhere that import didn’t with dogs_route. It feels like we’re doing a lot more work just to load up a simple function. Well, that’s because we haven’t created any dynamic functions yet…

Unquote

So let’s make this a bit more exciting. Remember we wanted index and show routes for all of our dogs, cats, and turtles? Well if we keep using our macro like we are now, they’re not really doing anything special for us. We defined a macro dogs_index that returns a string. We might as well have just written as a function in a module and used import.

By using unquote, we have access to variables insides of the macros so that we can create different functions depending on what variables are passed in. Take a look at this:

defmodule ControllerHelper do
defmacro create_link_helper(name) do
quote do
def unquote(:"#{name}_index")() do
unquote("/#{name}")
end
      def unquote(:"#{name}_show")(id) do
"/#{unquote(name)}/#{id}"
end
end
end
end
defmodule DogController do
require ControllerHelper
ControllerHelper.create_link_helper(:dogs)
  def index do
IO.puts dogs_index()
end
  def show do
dog = %{ id: 1, name: "Boo" }
IO.puts dogs_show(dog.id)
end
end

We can now run the following even though we never defined a function dogs_index/0 or dogs_show/1:

iex(1)> DogController.index()
/dogs
:ok
iex(2)> DogController.show()
/dogs/1
:ok

So depending on what we pass in to the create_link_helper, we are getting differently named functions that do different things. We use unquote() to access variables that are in the scope of the macro but would not be in the scope of the function that it’s creating. Think about it like this, when we’re calling the dogs_show/1 function, we need access to the ID of the dog so that we can create the string dogs/1, but we don’t need access to name which is just equal to the string "dogs".

So far so good? What does this all have to do with the final bit here? Use? Well, this is actually the simple part now.

Use

Use is actually just a shortcut for requiring a module and then calling the macro __using__(options) on it. So in this case, if we want to use that nice shorthand, all we have to do is make some small changes…

defmodule ControllerHelper do
defmacro __using__(name) do
quote do
def unquote(:"#{name}_index")() do
unquote("/#{name}")
end
    def unquote(:"#{name}_show")(id) do
"#{unquote(name)}/#{id}"
end
end
end
end
defmodule DogController do
use ControllerHelper, :dogs
  def index do
IO.puts dogs_index()
end
  def show do
dog = %{ id: 1, name: "Boo" }
IO.puts dogs_show(dog.id)
end
end

…and there you have it!

Wrap Up

And there you have it! These are some of the basic tools that Phoenix framework is using to make different parts of your code easily accessible across modules and how it is dynamically creating functions for you as well. Here is a quick recap of all of the tools we’ve gone over:

  • import — Makes a copy of a function or functions from one module into another
  • quote do — Takes in a block and returns an abstract representation of of the code
  • require — Loads up a module ahead of time so that macros can be compiled
  • unquote — Allows access to variables at the time the macro is creating a function
  • use — A shortcut for requireing a module and then calling a macro with the name __using__

I’ve really enjoyed digging in a bit and learning about these tools because it not only helps me better understand how and where functions come from in Phoenix, it also gives me insights and ideas as to how I can write better and more exciting Elixir code in the future. I know I’m even more excited to keep learning and working with Elixir and I hope you are as well!

Resources:

P.S.: Want to work on a mission-driven team that loves ice cream and builds Elixir apps? We’re hiring!

Footer top

To learn more about Flatiron School, visit the website, follow us on Facebook and Twitter, and visit us at upcoming events near you.

Flatiron School is a proud member of the WeWork family. Check out our sister technology blogs WeWork Technology and Making Meetup.

Footer bottom