How to Generate Custom Types with Macros

The post covering how to generate nifty types to use in @spec in compile time with macros.

https://rocket-science.ru/hacking/2020/07/15/generated-types

3 Likes

I’ve only skimmed it, but couldn’t find any parameterized type in there, all defined types are of arity 0, have I missed something?

It does not matter what arity are types, I meant one could parametrize call to use Scaffold with types, not parametrized types themselves.

Parameteized types would be handled for granted. I have removed “parametrized” from the tagline to avoid misunderstanding, thanks.

2 Likes

I think the code you are presenting in the post can be simplified greatly.
I believe the following works:

defmacro __using__(opts) do
  fields = opts[:fields]
  keys = Keyword.keys(fields)
  fields_with_struct_name = [__struct__: __CALLER__.module] ++ fields

  quote location: :keep do
    @type t :: %{unquote_splicing(fields_with_struct)}
    defstruct unquote(keys)
  end
end

But this is almost exactly what I present in Working Solution. Yes, I agree, that maybe unquote_splicing/1 migth to some extent look more exquisite, but I explicitly wanted to show a low-level approach, dealing with real AST instead.

Also, this approach wouldn’t allow propagated types (:version in my example,) because version: atom() outside of the quote would raise.

But what about adding them outside of the quote, rather than adding them to the quote you are writing anyway? Or maybe you might write them in their own dedicated quote before the final quoted AST that is returned?

The main reason to use the explicit AST-notation is to pattern-match on it (which you cannot do with quoted fragments because the meta fields of {name, meta, arguments} would often not match). In places where you are constructing AST it is virtually always more understandable to work with quote/unquote, in my humble opinion :slightly_smiling_face:.

1 Like

Eh. {^name, _, ^arguments} = ast?

Anyway, this is the top of the iceberg only, because the approach shown would fail if the arguments are not hardcoded (compile-time literals,) but e. g. passed as a module argument (use Scaffold, @params.)

I have this problem overcome as well, and for it, I was not able to stay high-level not descending to AST, but mentioning this would have made the text too huge. I am planning Part II. Down the rabbit hole, so stay tuned :slight_smile:

I meant more like for instance:

case expr of
   {:+, _, [lhs, rhs]} -> # ...
   {:my_fun, _, args} -> # ...
   # etc
end

Very true :blush:

I’m looking forward to it!

For the sake of consistency I posted it here: Elixir Blog Posts

1 Like