Question about visualizing Phoenix/Elixir functions, macros etc

I was perusing the Phoenix docs tonight and the section on routes contains the code below:

defmodule RouteExample do
  use HelloWeb, :verified_routes

  def example do
    ~p"/comments"
    ~p"/unknown/123"
  end
end
warning: no route path for HelloWeb.Router matches "/unknown/123"
  iex:5: RouteExample.example/0

{:module, RouteExample, ...}

If you look at the “example” function it has two lines of code.

    ~p"/comments"
    ~p"/unknown/123"

The last line is the output, what the hell is the next to last line doing? It’s just lingering there - doing nothing.

Reading the docs the function is demonstrating something called ~p route paths

In the module I see the :verified_routes atom. Does this somehow change the behavior of the Module so that the behavior of the function is different than simply returning the last line?

The use HelloWeb, :verified_routes line does not change the behaviour in the way that you’re suggesting, but you are right that it is affecting the current module, and it is what enables the verified routes to be, well, verified. The ~p sigil performs compile-time checks where each string representation of a route within such a sigil is compared against all the routes registered in your Phoenix router(s), and warns if one of the sigils references a route that doesn’t exist.

The example/0 function is here just for the sake of providing an example of two uses of the sigil. The behaviour of the example/0 function itself is not important, as the example demonstrates how these routes are verified at compile-time, before anything ever calls to example/0 at runtime. Instead of thinking of the sigil lines as plain values, think of them as two distinct function calls (sigil_p("/comments")) that can have side-effects.

If you’re curious about the details, here’s a brief explanation of what is happening here. I’m skimming over some parts, but the way through which this route verification essentially happens is:

  1. The use macro call HelloWeb, :verified_routes in the example is equivalent to lines require HelloWeb and HelloWeb.__using__(:verified_routes).
  2. Based on the atom, HelloWeb further dispatches your use to another module, in this case Phoenix.VerifiedRoutes.
  3. Phoenix.VerifiedRoutes.__using__/1 sets up the collection of values in ~p macros and ensures that the verification is done at compile-time, using a __before_compile__ compilation callback.
  4. On compilation, the setup done by step 3 emits a warning if any of the ~p sigil’s value does not match one of the registered routes.

For further reading about how all of this works, the following links can be interesting:

4 Likes

I may be misreading this but are you asking about the first line? Next to last is what confuses me when there is 2 lines there.

Elixir returns the results of the last expression of every function call so that first line is effectively ignored in the warning message because it isn’t returned.

If you’re asking why include an example like that, I’m equally baffled unless I’m missing something. It could be less jarring if there were multiple functions like example/0 and comments/0 each with their own route. You’d see 2 distinct warnings that would maybe match what your brain is expecting.

After saying all that, it’s also useful to be exposed to function result behavior as early as possible because it was also one of those “huh?” moments for me.

Try changing the ~p"/comments" line to ~p"/thing_that_definitely_does_not_exist" and see what happens.

That line is still being compiled, so the verified routes machinery can check to see if it’s valid.

I’m not running the code, It’s not mine. I am reading it from the Phoenix docs.