# Introducing Julia/Metaprogramming

 « Introducing JuliaMetaprogramming » Plotting Modules and packages

## 何为元编程?

- `@time` 巨集指令:

```julia> @time [sin(cos(i)) for i in 1:100000];
0.102967 seconds (208.65 k allocations: 9.838 MiB)
```

`@time` 巨集指令在代码的前面插入了 "秒表开始" 的命令在传入的表达式之前。当代码结束的时候，添加了一个“秒表结束” 的命令。然后进行计算，以报告所经过的时间和内存使用情况。

- `@which` 巨集指令

```julia> @which 2 + 2
+(x::T, y::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at int.jl:53
```

## 冒号表达式（Quoted expressions）

```julia> x = 3
3

julia> :x
:x
```

(如果您不熟悉编程中引用符号（Quoted Symbols）的用途，请想想在书写中如何使用引用来区分普通用途和特殊用途。例如，在句子中：

`'Copper' contains six letters.`

```julia> :(2 + 2)
:(2 + 2)
```

```quote
2 + 2
end
```

```quote
#= REPL[123]:2 =#
2 + 2
end```

```expression = quote
for i = 1:10
println(i)
end
end
```

```quote
#= REPL[124]:2 =#
for i = 1:10
#= REPL[124]:3 =#
println(i)
end
end```

`expression` 对象的类型是 `Expr`:

```julia> typeof(expression)
Expr
```

## 对表达式进行求值

Julia 还有一个函数 `eval()` 用于计算未求值的表达式：

```julia> eval(:x)
3
julia> eval(:(2 + 2))
4
julia> eval(expression)
1
2
3
4
5
6
7
8
9
10
```

```e = :(
for i in 1:10
println(i)
end
)
```

```:(for i = 1:10 # line 2:
println(i)
end)```

```julia> eval(e)
1
2
3
4
5
6
7
8
9
10
```

## 表达式的内部（Inside Expressions）

```P = quote
a = 2
b = 3
c = 4
d = 5
e = sum([a,b,c,d])
end
```

```quote
#= REPL[125]:2 =#
a = 2
#= REPL[125]:3 =#
b = 3
#= REPL[125]:4 =#
c = 4
#= REPL[125]:5 =#
d = 5
#= REPL[125]:6 =#
e = sum([a, b, c, d])
end```

```julia> fieldnames(typeof(P))
```

`head`字段为`:block` , `args`字段是一个数组，包含表达式(包括注释)。我们可以用这些简单的 Julia 技巧来检查这些。

```julia> P.args[2]
:(a = 2)
```

```for (n, expr) in enumerate(P.args)
println(n, ": ", expr)
end
```
```1: #= REPL[125]:2 =#
2: a = 2
3: #= REPL[125]:3 =#
4: b = 3
5: #= REPL[125]:4 =#
6: c = 4
7: #= REPL[125]:5 =#
8: d = 5
9: #= REPL[125]:6 =#
10: e = sum([a, b, c, d])```

```julia> eval(P)
14

julia> P.args[end] = quote prod([a,b,c,d]) end
quote
#= REPL[133]:1 =#
prod([a, b, c, d])
end

julia> eval(P)
120
```

```julia> P.args[end].args[end].args[1]
:sum

julia> P.args[end].args[end].args[1] = :prod
:prod

julia> eval(P)
120
```

### 抽象语法树（AST）

```julia> dump(:(1 * sin(pi/2)))
```
``` Expr
args: Array{Any}((3,))
1: Symbol *
2: Int64 1
3: Expr
args: Array{Any}((2,))
1: Symbol sin
2: Expr
args: Array{Any}((3,))
1: Symbol /
2: Symbol pi
3: Int64 2
typ: Any
typ: Any
typ: Any```

## 表达式插值

```julia> "the sine of 1 is \$(sin(1))"
"the sine of 1 is 0.8414709848078965"
```

``` julia> quote s = \$(sin(1) + cos(1)); end
```
```quote  # none, line 1:
s = 1.3817732906760363
end```

## 宏

```macro p(n)
if typeof(n) == Expr
println(n.args)
end
return n
end
```

```julia> @p 3
3
```

```julia> @p 3 + 4 - 5 * 6 / 7 % 8
Any[:-,:(3 + 4),:(((5 * 6) / 7) % 8)]
2.7142857142857144
```

Also notice that the macro `p` returned the argument, which was then evaluated, hence the `2.7142857142857144`. But it doesn't have to — it could return a quoted expression instead.

As an example, the built-in `@time` macro returns a quoted expression rather than using `eval()` to evaluate the expression inside the macro. The quoted expression returned by `@time` is evaluated in the calling context when the macro has done its work. Here's the definition:

```macro time(ex)
quote
local t0 = time()
local val = \$(esc(ex))
local t1 = time()
println("elapsed time: ", t1-t0, " seconds")
val
end
end
```

Notice the `\$(esc(ex))` expression. This is the way that you 'escape' the code you want to time, which is in `ex`, so that it isn't evaluated in the macro, but left intact until the entire quoted expression is returned to the calling context and executed there. If this just said `\$ex`, then the expression would be interpolated and evaluated immediately.

If you want to pass a multi-line expression to a macro, use the ` begin` ... ` end` form:

```@p begin
2 + 2 - 3
end
```
```Any[:( # none, line 2:),:((2 + 2) - 3)]
1```

(You can also call macros with parentheses similar to the way you do when calling functions, using the parentheses to enclose the arguments:

```julia> @p(2 + 3 + 4 - 5)
Any[:-,:(2 + 3 + 4),5]
4
```

This would allow you to define macros that accepted more than one expression as arguments.)

### `eval()` and `@eval`

There's an `eval()` function, and an `@eval` macro. You might be wondering what's the difference between the two?

```julia> ex = :(2 + 2)
:(2 + 2)

julia> eval(ex)
4

julia> @eval ex
:(2 + 2)
```

The function version (`eval()`) expands the expression and evaluates it. The macro version doesn't expand the expression you supply to it automatically, but you can use the interpolation syntax to evaluate the expression and pass it to the macro.

```julia> @eval \$(ex)
4
```

In other words:

```julia> @eval \$(ex) == eval(ex)
true
```

Here's an example where you might want to create some variables using some automation. We'll create the first ten squares and ten cubes, first using `eval()`:

```for i in 1:10
symbolname = Symbol("var_squares_\$(i)")
eval(quote \$symbolname = \$(i^2) end)
end
```

which creates a load of variables named `var_squares_n`, such as:

```julia> var_squares_5
25
```

and then using `@eval`:

```for i in 1:10
symbolname = Symbol("var_cubes_\$(i)")
@eval \$symbolname = \$(i^3)
end
```

which similarly creates a load of variables named `var_cubes_n`, such as:

```julia> var_cubes_5
125
```

Once you feel confident, you might prefer to write like this:

```julia> [@eval \$(Symbol("var_squares_\$(i)")) = (\$i^2) for i in 1:10]
```

## Scope and context

When you use macros, you have to keep an eye out for scoping issues. In the previous example, the `\$(esc(ex))` syntax was used to prevent the expression from being evaluated in the wrong context. Here's another contrived example to illustrate this point.

```macro f(x)
quote
s = 4
(s, \$(esc(s)))
end
end
```

This macro declares a variable `s`, and returns a quoted expression containing `s` and an escaped version of `s`.

Now, outside the macro, declare a symbol `s`:

```julia> s = 0
```

Run the macro:

```julia> @f 2
(4,0)
```

You can see that the macro returned different values for the symbol `s`: the first was the value inside the macro's context, 4, the second was an escaped version of `s`, that was evaluated in the calling context, where `s` has the value 0. In a sense, `esc()` has protected the value of `s` as it passes unharmed through the macro. For the more realistic @time example, it's important that the expression you want to time isn't modified in any way by the macro.

## Expanding macros

To see what the macro expands to just before it's finally executed, use the `macroexpand()` function. It expects a quoted expression containing one or more macro calls, which are then expanded into proper Julia code for you so that you can see what the macro would do when called.

```julia> macroexpand(Main, quote @p 3 + 4 - 5 * 6 / 7 % 8 end)
Any[:-,:(3 + 4),:(((5 * 6) / 7) % 8)]
quote
#= REPL[158]:1 =#
(3 + 4) - ((5 * 6) / 7) % 8
end
```

(The `#none, line 1:` is a filename and line number reference that's more useful when used inside a source file than when you're using the REPL.)

Here's another example. This macro adds a `dotimes` construction to the language.

```macro dotimes(n, body)
quote
for i = 1:\$(esc(n))
\$(esc(body))
end
end
end
```

This is used as follows:

```julia> @dotimes 3 println("hi there")
hi there
hi there
hi there
```

Or, less likely, like this:

```julia> @dotimes 3 begin
for i in 4:6
println("i is \$i")
end
end
```
```i is 4
i is 5
i is 6
i is 4
i is 5
i is 6
i is 4
i is 5
i is 6
```

If you use `macroexpand()` on this, you can see what happens to the symbol names:

```macroexpand(Main, # we're working in the Main module
quote
@dotimes 3 begin
for i in 4:6
println("i is \$i")
end
end
end
)
```

with the following output:

```quote
#= REPL[160]:3 =#
begin
#= REPL[159]:3 =#
for #101#i = 1:3
#= REPL[159]:4 =#
begin
#= REPL[160]:4 =#
for i = 4:6
#= REPL[160]:5 =#
println("i is \$(i)")
end
end
end
end
end
```

The `i` local to the macro itself has been renamed to `#101#i`, so as not to clash with the original `i` in the code we passed to it.

## A more useful example: @until

Here's how to define a macro that is more likely to be useful in your code.

Julia doesn't have an until condition ... do some stuff ... end statement. Perhaps you'd like to type something like this:

```until x > 100
println(x)
end
```

You'll be able to write your code using the new `until` macro like this:

```until <condition>
<block_of_stuff>
end
```

but, behind the scenes, the work will be done by actual code with the following structure:

```while true
<block_of_stuff>
if <condition>
break
end
end
```

This forms the body of the new macro, and it will be enclosed in a `quote` ... `end` block, like this, so that it executes when evaluated, but not before:

```quote
while true
<block_of_stuff>
if <condition>
break
end
end
end
```

So the nearly-finished macro code is like this:

```macro until(<condition>, <block_of_stuff>)
quote
while true
<block_of_stuff>
if <condition>
break
end
end
end
end
```

All that remains to be done is to work out how to pass in our code for the `<block_of_stuff>` and the `<condition>` parts of the macro. Recall that `\$(esc(...))` allows code to pass through 'escaped' (i.e. unevaluated). We'll protect the condition and block code from being evaluated before the macro code runs.

The final macro definition is therefore:

```macro until(condition, block)
quote
while true
\$(esc(block))
if \$(esc(condition))
break
end
end
end
end
```

The new macro is used like this:

```julia> i = 0
0

julia> @until i == 10 begin
global i += 1
println(i)
end
```
```1
2
3
4
5
6
7
8
9
10
```

or

```julia> x = 5
5

julia> @until x < 1 (println(x); global x -= 1)
5
4
3
2
1
```

## Under the hood

If you want a more complete explanation of the compilation process than that provided here, visit the links shown in Further Reading, below.

Julia performs multiple 'passes' to transform your code to native assembly code. As described above, the first pass parses the Julia code and builds the 'surface-syntax' AST, suitable for manipulation by macros. A second pass lowers this high-level AST into an intermediate representation, which is used by type inference and code generation. In this intermediate AST format all macros have been expanded and all control flow has been converted to explicit branches and sequences of statements. At this stage the Julia compiler attempts to determine the types of all variables so that the most suitable method of a generic function (which can have many methods) is selected.

## 延伸阅读

• Julia Introspects（英文） Leah Hanson 2013 写的关于 Julia 内部表示的文章
 « Introducing JuliaMetaprogramming » Plotting Modules and packages