Fun with Elixir Macros

Fun with Elixir Macros

Lightning talk from ElixirConfEU 2018

9fae8c5d475fe322a3a74e53d56ee2a0?s=128

Tymon Tobolski

April 16, 2018
Tweet

Transcript

  1. Fun with Macros Tymon Tobolski GitHub: @teamon Twitter: @iteamon

  2. defmodule AppRouter do use Plug.Router plug :match plug :dispatch get

    "/hello" do send_resp(conn, 200, "world") end match _ do send_resp(conn, 404, "oops") end end
  3. defmodule Cabify do use Tesla plug Tesla.Middleware.Base, "https://cabify.com" plug Tesla.Middleware.JSON

    def jobs do request(:get, "/jobs") end end
  4. 1. Accumulate middleware (plug Module) 2. Runtime execution

  5. defmodule Tesla do defmacro __using__(_) do quote do Module.register_attribute(__MODULE__, :middleware,

    accumulate: true) @before_compile Tesla import Tesla, only: [plug: 1, plug: 2] def request(method, path), do: Tesla.request(__MODULE__, {method, path}) end end # ...
  6. defmacro plug(module, opts \\ []) do quote do @middleware {unquote(module),

    unquote(opts)} end end
  7. defmodule Cabify do Module.register_attribute(__MODULE__, :middleware, accumulate: true) @before_compile Tesla @middleware

    {Tesla.Middleware.Base, "https://cabify.com"} @middleware {Tesla.Middleware.JSON, []} end
  8. defmacro __before_compile__(env) do middleware = Module.get_attribute(env.module, :middleware) quote do def

    __middleware__, do: unquote(middleware) end end
  9. defmodule Cabify do # ... def __middleware__ do [ {Tesla.Middleware.Base,

    "https://cabify.com"}, {Tesla.Middleware.JSON, []} ] end end
  10. defmodule Tesla do def request(module, env) do stack = module.__middleware__

    run(env, stack) end def run(env, []), do: make_request(env) def run(env, [{module, opts} | rest]), do: apply(module, :call, [env, rest, opts]) end
  11. defmodule Tesla.Middleware.Base do def call({method, path}, next, base) do Tesla.run({method,

    base <> path}, next) end end defmodule Tesla.Middleware.JSON do def call({method, path}, next, _opts) do Tesla.run({method, path <> ".json"}, next) end end
  12. def jobs do request(:get, "/jobs") end Cabify.jobs() # => "HTTP

    get https://cabify.com/jobs.json"
  13. - Simple & powerful API - call(env, next, opts) -

    Allows hot reloading of middlewares on production system - Full implementation at github.com/teamon/tesla (be sure to use 1.0.0-beta.1) - Don’t be afraid of macros :)