I'm on step zero on macros

Hi, I have read most of the documentation, and trying to get my head around macros and how to write my own, but I read a thing like this

macro css_str(x::String)
     kvalues::Vector{SubString} = split(x, ";")
     Dict{Symbol, String}(begin
          splt = split(kval, ":")
          Symbol(splt[1]) => string(splt[2])
end for kval in kvalues)::Dict{Symbol, String}
end

And really simply do not get what is happening. Can you send me to a clear, concise guide to macros?

2 Likes

What are you trying to accomplish with a macro? It’s easier to give advice if you have a concrete goal in mind.

The first rule of writing macros is that you should normally not write macros.

10 Likes

I’m pretty sure OP is going through a Medium article, which I cannot access as a non-member.

css_str splits a ;-delimited string into entries, then it splits those entries into key-value pairs in a dictionary:

julia> css"a:bc;d:ef" # doesn't handle spaces
Dict{Symbol, String} with 2 entries:
  :a => "bc"
  :d => "ef"

You can do the same thing with a function, and in the vast majority of practical cases, you’d want it done by a function because macros are more limited. The importance of macros is that they work at parse-time.

What is parse-time? The Metaprogramming docs explains it but I’ll sum it up quickly. Everything starts as source code, the text you type. The parser has to change that into data Julia can understand. It is here that you encounter syntax errors; the parser realized the source code isn’t valid Julia.

Parsing source code doesn’t make many data types. Most code turns into Symbol and Expr. Literal values for Int, Float64, and String are also present; literal means you have to write the value directly in the source code. A macro’s inputs are limited to these types, though it’s acceptable to output any type. css_str thus only works if you write something like css"a:bc;d:ef"; if you had x="a:bc;d:ef", there is no way to pass the variable x into css_str because only the symbol :x exists at parse-time.

So why use a macro if its inputs are limited? css_str is only a demonstration, it’s not representative of most macros. The usual use of macros is to transform code, Expr to Expr; a macro call would be equivalent to you manually writing the output into the source code. After a macro is done, parse-time is over and Julia evaluates the output. If it’s a Symbol or Expr, it gets evaluated like any untransformed code you wrote; if it is or contains any other types, those types just pass through.

Here’s another demonstration (read: impractical) of a macro. Say I want a log5 for convenience, but there’s no built-in one. Simple, I’ll just use the log identity:

julia> log5(x) = log2(x)/log2(5)

Let’s look at what a call would be lowered to (a step before compiling):

julia> @code_lowered log5(5)
CodeInfo(
1 ─ %1 = Main.log2(x)
│   %2 = Main.log2(5)
│   %3 = %1 / %2
└──      return %3
)

Hm, it’s doing log2(5), but we could do that in advance. But I don’t want make a const global variable just for that. With a macro specially designed to work on Expr interpolated with more types or non-literals, I can compute the value before inserting it right into the method Expr:

julia> @eval log5(x) = log2(x)/$(log2(5))

And now the lowered code does:

julia> @code_lowered log5(5)
CodeInfo(
1 ─ %1 = Main.log2(x)
│   %2 = %1 / 2.321928094887362
└──      return %2
)

Now for the impracticality, Julia’s compiler is smart enough to do function calls on constants or static parameters, including log2(5), at compile-time if inferred and simple, and most constants are used more than once so they are actually worth const global variables. There are better examples of @eval code generation in the docs.

3 Likes