stephanos

stephanos

Exploring the limitations of macros and module attributes

Hi!

I’ve been tinkering with bringing Clojure.spec to Elixir (I’m aware of prior art here, but want to experiment with a different syntax).

Having never dabbled in meta programming before, I’m curious to learn if I hit a wall or if I’m missing something.

Here is some simple pseudo-code

  @arg a: is_integer(), is_integer()
  @arg b: is_integer()
  @ret is_integer()
  def add(a, b) do
    a + b
  end

I discovered that @ is a macro in the Kernel module and appears to be evaluated before I get a chance to do anything (via before_compile, on_definition or my macro). Right now it fails because is_integer() isn’t valid.

Is there any way to transform the AST to replace the module attribute with my own code?

I’ve learned about the possibility of a custom defmodule implementation but that seems excessive to me. I considered using function references, like &is_integer/1 but it seems a bit ugly to me (very subjective ;). It also appears that I can’t override the @ macro easily since I’m getting “call is ambiguous” when I define my own. Are these all available options?

Thank you!
Stephan

Most Liked

Qqwy

Qqwy

TypeCheck Core Team

Overriding the @-macro is possible, and is in fact what Norm does to allow custom macro-expansion in the ‘@contract …’ notation. (c.f. norm/lib/norm/contract.ex at master · elixir-toniq/norm · GitHub)

I do think that this solution is a bit of a hack. For instance, it would not be possible to use multiple libraries that each try to override @ unless they pay special attention to how they do the fall-back.

christhekeele

christhekeele

Hey, welcome to the ElixirForum, and Elixir meta-programming!


I think the conceptual nudge that will help you the most here is a little clarification in the role of various concepts during the compilation process:

  • a Module is a unit of compilation + a namespace
  • a module attribute is a constant language-level value determined at compile time
  • a Macro is a special function executed at compile time that receives AST as input and places its return value into the AST where invoked
  • macros can access module attributes, ie in compilation hooks via attribute functions.

Emphasis on value. What you place in a module attribute should be a language-level primitive value, that may be operated on later by compile-time hooks. You’ll notice the input to all documented module attributes, with the exception of type-related stuff, are just tuples, lists, strings, atoms, etc.

Typespec-related attributes get special builtin compiler-powered semantics and expressivity, that I do not think you can tap into in user-land code.

You cannot place arbitrary typespec syntax into an arbitrary module attribute today.


The easiest way to get this is to use macros, which specialize in AST transformation. You should try offering, for instance, defarg and defret macros. They could emit type-spec related module attributes AST compatible with the type system. Macros are more than capable of consuming the typespec syntax and operating on it, doing library magic with it, and re-emitting the syntax into typespec module attributes.

As an alternative, you could invent a language-level data primitive syntax that expresses what you want, like @arg a: [:is_integer, :is_integer], and compile it into a typespec via a compilation hook (and perform your library’s magic in the same pass).

The takeaway here, though, is that you’ll need to leverage macros get what you want, with module attributes as possible input to them and output from them.


One note:

You definitely don’t want to do this–even if it is possible (which would surprise me) overriding module attribute definition would have a high chance of breaking tons of stuff in caller code. @ is a macro in the Kernel module for documentation and compiler bootstrapping purposes only.

stephanos

stephanos

@christhekeele Thank you, I really appreciate that insight - and the warm welcome!

@Qqwy ha, I guess I wasn’t aware of this Clojure.spec-like implementation - that’s pretty close to what I was imagining. The generator-side of this is what I find the most intriguing.

Where Next?

Popular in Discussions Top

andre1sk
A big advantage to Elixir is all the distributed goodness but for many applications running on multiple nodes having integrated Etcd, Zoo...
New
Fl4m3Ph03n1x
Background A few days ago I was listening to The future of Elixir from Elixir Talks, with Dave Thomas (@pragdave ) and Brian Mitchell. I...
New
WolfDan
After doing a port from a c++ library to my project in phoenix I’ve seen that I need a faster way to run this algorithm and I found this ...
New
crispinb
On reading dhh’s latest The One Person Framework it strikes me that Phoenix with LiveView is already pretty much this. However, never hav...
New
AlexMcConnell
The reason that Rails is as popular as it is is because it’s very easy for relatively inexperienced developers to get a lot of work done....
588 19568 166
New
marciol
Please, let me know if this kind of discussion already took place in another topic . Hi all, how do you consider if is better to build ...
New
eteeselink
Hi all, In the last days, two things happened: A blog post titled “They might never tell you it’s broken” made the rounds. It’s about ...
New
ben-pr-p
In general I’ve been sticking to this community style guide GitHub - christopheradams/elixir_style_guide: A community driven style guide ...
New
scouten
I’m looking for a host for the server part of a small (personal) side project that I’m working on. It’s currently written in Node.js and ...
New
opsb
We’re considering our architecture from a viewpoint of scaling our traffic heavily over the next 6 months. Our current deployment is runn...
New

Other popular topics Top

TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41539 114
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod – where is this set? Thanks.
New
josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New

We're in Beta

About us Mission Statement