Julia learning macros / metaprogramming

Hey Julianers,

I am trieing to understand the julia macros, but I just can’t understand when do I have to use esc, quote, $, eval or so…

Can someone give me the easiest tutorial I can understand? How do you know when to use what?

It would be really nice to have an easier explaination of every command there, to catch the flow. :slight_smile:

The manual is great for this: https://docs.julialang.org/en/v1/manual/metaprogramming/

2 Likes

Heres a short overview.
Note: I wrote this on my phone and haven’t been able to test it so there may be some bugs in the sample code.

quote or :( )
Use it for constructing blocks of code.
edit: added missing esc

macro shared_fields()
  esc(quote
    field1::Int
    field2::Float64
  end)
end

struct astruct
  @shared_fields()
  field3::Uint
end

struct bstruct
  @shared_fields()
  field3::String
end

$
Splices in code

macro double_input(fn, x)
  quote
    # If x is an expression with side effects like `print(b)`
    # we don't want to do that twice so we assign it to `z` first.
    z = $(x)
    $(fn)(z, z)
  end
end

@double_input div 3
# returns 1

esc
Use it for variables assigned to in macros that you want visible externally

macro setvar(x, y)
  quote
    $(esc(x)) = $(y)
  end
end

Meta.quot
Allows you to splice in code without interpolating it.

macro print_code(x)
  :(println($(Meta.quot(x))))
end

eval
Shouldn’t be used in macros or functions for the most part. It can be however useful to use at top level if you have a large number of similar functions to define.

struct MyType
  x::Int64
end

for op in [:(+), :(-), :(*), :(/)]
  @eval Base.$(op)(x::MyType, y::MyType) = MyType($(op)(x.x, y.x))
end
8 Likes

If you google “julia metaprogramming tutorial” you will find this 2018 workshop by @andyferris. See also my 2019 talk on why you shouldn’t use metaprogramming most of the time.

10 Likes

I wrote two macros tutorials at https://github.com/johnmyleswhite/julia_tutorials/

12 Likes

Wow guys, these personal macro tutorials and workshop are awesome!

Thank you!

4 Likes

I also have a couple of tutorials:

and

The video is from 2016 so some syntax has changed, but most of the metaprogramming section is still valid.

4 Likes

This particular example does not work:

ERROR: syntax: field name "Main.field1" is not a symbol around REPL[2]:1
Stacktrace:
 [1] top-level scope at REPL[2]:1

I was using eval for something similar, but it seems that if a macro could be used for that, it would be better. What is the best way to share fields between structs?

I’d encourage you to read about macro hygeine to see what’s going on here:

julia> macro shared_fields()
         ex = quote
           field1::Int
           field2::Float64
         end
         esc(ex)
       end
@shared_fields (macro with 1 method)

julia> struct astruct
         @shared_fields()
         field3::UInt
       end

julia> fieldnames(astruct)
(:field1, :field2, :field3)

The macro @macroexpand is also quite useful for debugging these things:

julia> macro shared_fields1()
           quote
               field1::Int
               field2::Float64
           end
       end
@shared_fields1 (macro with 1 method)

julia> macro shared_fields2()
           quote
               field1::Int
               field2::Float64
           end |> esc
       end
@shared_fields2 (macro with 1 method)

julia> @macroexpand @shared_fields1
quote
    #= REPL[13]:3 =#
    Main.field1::Main.Int
    #= REPL[13]:4 =#
    Main.field2::Main.Float64
end

julia> @macroexpand @shared_fields2
quote
    #= REPL[14]:3 =#
    field1::Int
    #= REPL[14]:4 =#
    field2::Float64
end
2 Likes

Yep, it was missing an esc I have gone back and updated the example and added a note that it was edited.

2 Likes

I just realized there was actually a mistake in that section of the documentation that I linked to so I edited my comment to use the 1.6-dev docs that have fixed the mistake. @leandromartinez98 , you may want to use the updated link so it makes more sense.

2 Likes

I think these contents are actually very useful for anyone who wants to understand the julia macros.
Don’t you guys think we should collect the resources at the end of the https://docs.julialang.org/en/v1/manual/metaprogramming/ ?
And by time someone will definitely group these information and provide some extension to the understanding of the topic?

I thought many times that we should have a “Julia by examples” docs, built from community experiences. To be sincere, I have read many times the docs as they are, for example this metaprogramming section, and I have a hard time understanding what those things are for. I am not a computer scientist, and most times I only get the idea of what the experts are trying to explain after seeing a few examples of the functions in different contexts. After I understand how things work, when I read the docs again, I find that they are most of the time perfectly written, but a dedicated docs only for examples would be a great addition. This is how we do for plots, isn’t it?

On the other hand, perhaps the search history of this same discourse is going to achieve that goal.

2 Likes

I think the manual, and particularly that section, would benefit a lot from more examples, practical tips, common use cases, pitfalls.

Incremental additions are great, but I hope one of these days someone knowledgeable about Julia’s metaprogramming will sit down and do a major rewrite. I used Common Lisp macros extensively for years, but I still think of Julia’s macros as a minefield.

6 Likes