budgie

budgie

Defining nested macros for a DSL? How can you share state?

I’m trying to build a macro for edge definitions in a graph. How can I access parameters from the outer macro inside the inner macro?

  1. The direction macro must define a module with the block passed into schema.
  2. The schema macro must implicitly be able to access the “accesses” string passed into the top-level edge macro.

How can I define complex multi-level macros like this for building DSLs but have the state be shared like I described?

This is the edge module:

defmodule App.Edge.Accesses do
  import App.Edge.EdgeType

  alias App.Users.User
  alias App.Invitations.Invitation
  alias App.Tenants.Tenant

  edge "accesses" do
    direction from: User, to: Invitation
    direction from: Invitation, to: Tenant

    schema do
    end
  end
end

This is the EdgeType module with all of the macros:

defmodule Quota.Edge.EdgeType do
  defmacro edge(collection_name, do: do_block) do
    quote do
    end
  end

  defmacro direction(from: from, to: to) do
    quote do
    end
  end

  defmacro schema(do: do_block) do
    quote do
    end
  end
end

Most Liked

zachdaniel

zachdaniel

Creator of Ash

In spark, this DSL would look something like this:

defmodule App.Edge.EdgeType.Dsl do
  @schema ... (left out for brevity)

  @direction %Spark.Dsl.Entity{
    name: :direction,
    schema: [
      from: [type: :module, required: true],
      to: [type: :module, required: true]
    ]
  }
  
  defmodule Edge do
    defstruct [:name, :directions]
  end

  @edge %Spark.Dsl.Entity{
    name: :edge,
    target: Edge,
    schema: [
      name: [
        type: :atom,
        required: true
      ],
      entities: [
        directions: [@direction],
        schema: [@schema]
      ],
     singleton_entity_keys: [:schema]
    ]
  }

  @edges %Spark.Dsl.Section{
    name: :edges,
    top_level?: true,
    entities: [
      @edge
    ]
  }
  
  use Spark.Dsl.Extension, sections: [@edge]
end

defmodule App.Edge.EdgeType do
  use Spark.Dsl,
    default_extensions: [extensions: [App.Edge.EdgeType,Dsl]]
end

defmodule EdgeType.Info do
  use Spark.InfoGenerator, extension: App.Edge.EdgeType.Dsl, sections: [:edge]
end

With the above (I left some stuff out, and probably got a few things wrong since I typed this all in a forum window and didn’t test it), the syntax you’ve laid out should work as it is.

defmodule App.Edge.Accesses do
  use App.Edge.EdgeType # but you'd need to `use` instead of `import`

  alias App.Users.User
  alias App.Invitations.Invitation
  alias App.Tenants.Tenant

  edge "accesses" do
    direction from: User, to: Invitation
    direction from: Invitation, to: Tenant

    schema do
    end
  end
end
zachallaun

zachallaun

A somewhat common option often used for “contexts” like this is to use module attributes, where each macro would push their context into the attribute prior to injecting the body block and then pop it out after. Each inner macro can access the current context through that attribute.

I’m on my phone so can’t easily provide a more complete example, but basically the idea is to inject a call to Module.put_attribute at the beginning of the quote block, and then

Here’s an example (untested):

@edge_context :"#{__MODULE__}.edge"

defmacro edge(collection, do: quoted) do
  quote do
    prev = Module.get_attribute(__MODULE__, unquote(@edge_context), [])

    Module.put_attribute(__MODULE__, unquote(@edge_context), [unquote(collection) | prev])

    result = unquote(quoted)

    Module.put_attribute(__MODULE__, unquote(@edge_context), prev)

    result
  end
end

defmacro schema(do: quoted) do
  context = Module.get_attribute(__CALLER__.module, @edge_context)
  
  ...
end

Where Next?

Popular in Questions Top

sergio
In Ruby, I can go: User.find_by(email: "foobar@email.com").update(email: "hello@email.com") How can I do something similar in Elixir? ...
New
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
chensan
I have a User schema with a :from_id field set to type :string: defmodule TweetBot.Repo.Migrations.CreateUsers do use Ecto.Migration ...
New
LegitStack
I’m trying to make a websocket server in Phoenix or raw Elixir. I heard about gun, I think I could use cowboy, but since I’m not that sma...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
Mooodi
Given a string, how can I get access to its character by index? Enum.at("my_string", 2) doesn't work. Or rather, not char, but a substr...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New
idi527
I’ve been re-reading swift book again and noticed that multiline strings there don’t have a trailing line break, unlike in elixir iex(2)...
New

Other popular topics Top

srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call th...
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
myronmarston
The Elixir Typespec docs show the following syntax for keyword lists in typespecs: # ... | [key: type] # keyword lis...
New
AstonJ
by Lance Halvorsen Elixir and Phoenix are generating tremendous excitement as an unbeatable platform for building modern web application...
460 27162 124
New

We're in Beta

About us Mission Statement