mmport80

mmport80

The Scalability of Macros?

Much smarter people than me value the macro concept - and from my own shallow understanding - I appreciate what I can see in Phoenix and other libraries like Absinthe etc.

However, I recently had a simple problem using this macro,

Phoenix.ConnTest.post

All I needed was to share it amongst tests. The tests would sometimes work with it in a shared library function file, sometimes not. Frustrating.

In the end I switched to the function,

Phoenix.ConnTest.dispatch

and everything was good.

When I have a function-first API I can do well defined things. When I have macros things often seem harder to compose together, and scale.

I am making extensive use of Absinthe, and suspect with a little more time, thought and fewer macros I could more efficiently abstract my schema & type code also…

Certainly macros have their place, but they seem to chose a level of abstraction a priori, which doesn’t fit well always (especially with larger projects).

Most Liked

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

This is a good question, and I think there’s a fair bit that could be said here. For the moment though I just want to elaborate a bit on Absinthe’s use of macros.

Whereas some macros get used like functions, the Absinthe schema macros exist largely to just build datastructures in a compile time optimized format, where doing so manually would be extremely verbose. We do actually have a set of plans for refactoring schema creation into a more data driven approach, but that data is generally not the kind you’d want to write out by hand. There is sort of a supplemental spec for writing GraphQL schemas as strings that has developed that lets you write them like:

type User {
  id: ID!
  name: String
}

We can already parse this into the internal structures that Absinthe works on called Blueprints, but there isn’t a way to build an actual schema with it yet. The goal then is to have BOTH strings like this as well as the schema macros build blueprint structures, which are then just ordinary data you can manipulate at will. Ultimately however no matter how that structure gets built we need to compile it into an actual Elixir module, because it gets us incredibly good key value lookup characteristics.

Long story short: Yes, the mechanics of doing macros within Absinthe does have some limitations, and we hope that some of the changes we’re planning will help with those limitations. On the other hand, because the Absinthe macros are essentially datastructure short-hand (instead of function like) we’ve really seen very few issues crop up over the last few years.

christhekeele

christhekeele

The same can be said for Plug’s router DSL that creates incredibly optimized request dispatching, or the Ecto schema DSL, whose usage allows for the incredibly slick Ecto query DSL, that allows Ecto to generate your queries at compile time instead of runtime.

I find DSLs are overused and misused in many languages, but the places where they are best used in Elixir tend to follow this pattern of describing very complicated things in elegant fashions so that they can be converted into very useful compile-time forms that can fully leverage the power of pattern matching and issue important warnings earlier than runtime variants would.

__using__ macros is another a good example of a macro use-case that consistently works well, and it’s worth noting that they too are never intended to be interfaced with like a chainable function.

Perhaps that’s good heuristic for when a macro isn’t really necessary. The macros that are intended to be called more like standard functions, like the Phoenix testing ones above, often just serve to reduce boilerplate in ways that can’t quite be accomplished normally. I think that’s a laudable goal, but even though I never have experienced the issues mentioned here with them, I often find myself preferring straightforward functional variants over macro helpers. What’s important in these use-cases is that both paths are viable, I think, like dispatch proved to be.

mmport80

mmport80

v good pt!

christhekeele

christhekeele

Another really cool example of function-esque macros being used to do unique compile time work are Logger’s debug, warn, info, and error macros (combined with the :compile_time_purge_level option).

You can get the same effect just by calling the log(level, msg) function, but when you have configured your app to emit logs at higher levels, the lower macro level calls will be entirely compiled away, instead of just no-oping. This is really a glorious boon for log-heavy or performance-sensitive applications, especially in situations where your log message is non-trivial to generate, in a tight inner loop, or highly dynamic or large enough such that it has poor garbage collection characteristics.

dorgan

dorgan

I think the Ash framework works this way(from what I understand of conversations in the discord server).

You define your data with a macro DSL and then the framework leverages it to build a lot of stuff. Whenever you need to access to the underlying data generated by ash, you can use a set of instrospection functions, like attribute/1. This is similar to how Ecto let’s you instrospect your schema, except in Ecto you have to write your validation functions yourself, while Ash can leverage the data you provide it with to validate things automatically.

Where Next?

Popular in Questions Top

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
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
Jim
As a follow up to my earlier question: I have the code compiling and running but not getting a successful login from the rest server. ...
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
lk-geimfari
What is most correct way to open, read and parse JSON file with poison? For example if we have example.json file in root of some projec...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
mathew4509
I have a list say x = ["23gh", "56kh", "97mh"] I would like to pass each element to Val in each iteration. Say, in iteration 1 -------...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers' Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New

Other popular topics Top

Tee
can someone please explain to me how Enum.reduce works with maps
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
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? https://hexdocs.pm/ecto/Ecto.Repo.h...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
9mm
I am constructing a JSON object (map) and I need to conditionally set a field. I’m trying to write proper elixir-way code… and I’m at a l...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
273 38985 115
New

We're in Beta

About us Mission Statement