|
1 | | -# [Lab 06: Macros](@id macro_lab) |
| 1 | +# [Lab 07: Macros](@id macro_lab) |
| 2 | +In this lab we are goinging to explore avenues of where macros can be useful |
| 3 | +- convenience (`@repeat n code`, `@show`) |
| 4 | +- code generation (`@define animal`) |
| 5 | +- syntactic sugar (`@world`) |
| 6 | +- performance critical applications (`@poly`) |
| 7 | + |
| 8 | +## Show macro |
| 9 | +Let's start with dissecting the simple `@show` macro, which allows us to demonstrate advanced concepts of macros |
| 10 | +- true quoting |
| 11 | +- escaping |
| 12 | +- interpolation |
| 13 | + |
| 14 | +```@repl lab07_show |
| 15 | +x = 1 |
| 16 | +@show x + 1 # equivalent to |
| 17 | +let y = x + 1 |
| 18 | + println("x + 1 = ", y) |
| 19 | + y # show macro also returns the result |
| 20 | +end |
| 21 | +
|
| 22 | +@show x = 3 |
| 23 | +let y = x = 2 |
| 24 | + println("x = 2 = ", y) |
| 25 | + y |
| 26 | +end |
| 27 | +x |
| 28 | +``` |
| 29 | +We have to both evaluate the code and show the expression as a string, which cannot be done easily in the realm of normal functions, however macro has programatic access to the code and thus also the name of the variable. We can use `@macroexpand` to see what is happening: |
| 30 | +```@repl lab07_show |
| 31 | +@macroexpand @show x + 1 |
| 32 | +``` |
| 33 | +Compared to the original [implementation](https://github.com/JuliaLang/julia/blob/ae8452a9e0b973991c30f27beb2201db1b0ea0d3/base/show.jl#L946-L959) |
| 34 | +```julia |
| 35 | +""" |
| 36 | + @show |
| 37 | +Show an expression and result, returning the result. See also [`show`](@ref). |
| 38 | +""" |
| 39 | +macro show(exs...) |
| 40 | + blk = Expr(:block) |
| 41 | + for ex in exs |
| 42 | + push!(blk.args, :(println($(sprint(show_unquoted,ex)*" = "), |
| 43 | + repr(begin local value = $(esc(ex)) end)))) |
| 44 | + end |
| 45 | + isempty(exs) || push!(blk.args, :value) |
| 46 | + return blk |
| 47 | +end |
| 48 | +``` |
| 49 | +Though this looks complicated we can boil it down in the following `@myshow` macro. |
| 50 | +```julia |
| 51 | +macro myshow(ex) |
| 52 | + :(println($(QuoteNode(ex)), " = ", repr(begin local value = $(esc(ex)) end))) |
| 53 | +end |
| 54 | + |
| 55 | +@myshow xx = 1 + 1 |
| 56 | +xx # should be defined |
| 57 | +``` |
| 58 | +Notice the following: |
| 59 | +- `QuoteNode(ex)` is used to wrap the expression inside another layer of quoting, such that when it is interpolated into `:()` it stays being a piece of code instead of the value it represents |
| 60 | +- `esc(ex)` is used in case that the expression contains an assignment, that has to be evaluated in the top level module `Main` (we are `esc`aping the local context) |
| 61 | +- `$(QuoteNode(ex))` and `$(esc(ex))` is used to evaluate an expression into another expression. |
| 62 | +All of the macros here should be hygienic. |
| 63 | + |
| 64 | +## Repeat macro |
| 65 | +In the lecture on profiling we have sometimes needed to run some code multiple times in order to gather some samples and we have tediously written out simple for loops inside functions such as this |
| 66 | +```julia |
| 67 | +function run_polynomial(n, a, x) |
| 68 | + for _ in 1:n |
| 69 | + polynomial(a, x) |
| 70 | + end |
| 71 | +end |
| 72 | +``` |
| 73 | + |
| 74 | +We can simplify this by creating a macro that does this for us. |
| 75 | +```@raw html |
| 76 | +<div class="admonition is-category-exercise"> |
| 77 | +<header class="admonition-header">Exercise</header> |
| 78 | +<div class="admonition-body"> |
| 79 | +``` |
| 80 | +Define macro `@repeat` that takes two arguments, first one being the number of times a code is to be run and the other being the actual code. |
| 81 | +```julia |
| 82 | +julia> @repeat 3 println("Hello!") |
| 83 | +Hello! |
| 84 | +Hello! |
| 85 | +Hello! |
| 86 | +``` |
| 87 | + |
| 88 | +```@raw html |
| 89 | +</div></div> |
| 90 | +<details class = "solution-body"> |
| 91 | +<summary class = "solution-header">Solution:</summary><p> |
| 92 | +``` |
| 93 | + |
| 94 | +```julia |
| 95 | +macro repeat(n::Int, ex) |
| 96 | + :(for _ in 1:$n |
| 97 | + $(esc(ex)) |
| 98 | + end) |
| 99 | +end |
| 100 | +``` |
| 101 | + |
| 102 | +```@raw html |
| 103 | +</p></details> |
| 104 | +``` |
| 105 | + |
| 106 | +## [Polynomial macro](@id lab07_polymacro) |
| 107 | +This is probably the last time we are rewritting the `polynomial` function, though not quite in the same way. We have seen in the last lab, that some optimizations occur automatically, when the compiler can infer the length of the coefficient array, however with macros we can generate code optimized code directly (not on the same level - we are essentially preparing already unrolled/inlined code). |
| 108 | + |
| 109 | +Ideally we would like write some macro `@poly` that takes a polynomial in a mathematical notation and spits out an anonymous function for its evaluation, where the loop is unrolled. |
| 110 | + |
| 111 | +*Example usage*: |
| 112 | +```julia |
| 113 | +julia> p = @poly x 3x^2 + 2x^1 + 10x^0 # the first argument being the independent variable to match |
| 114 | +``` |
| 115 | + |
| 116 | +However in order to make this happen, let's first consider much simpler case of creating the same but without the need for parsing the polynomial as a whole and employ the fact that macro can have multiple arguments separated by spaces. |
| 117 | + |
| 118 | +```julia |
| 119 | +julia> p = @poly 3 2 10 |
| 120 | +``` |
| 121 | + |
| 122 | +```@raw html |
| 123 | +<div class="admonition is-category-exercise"> |
| 124 | +<header class="admonition-header">Exercise</header> |
| 125 | +<div class="admonition-body"> |
| 126 | +``` |
| 127 | +Create macro `@poly` that takes multiple arguments and creates an anonymous function that constructs the unrolled code. Instead of directly defining the macro inside the macro body, create helper function `_poly` with the same signature that can be reused outside of it. |
| 128 | + |
| 129 | +```@raw html |
| 130 | +</div></div> |
| 131 | +<details class = "solution-body"> |
| 132 | +<summary class = "solution-header">Solution:</summary><p> |
| 133 | +``` |
| 134 | + |
| 135 | +```julia |
| 136 | +macro poly(a...) |
| 137 | + return _poly(a...) |
| 138 | +end |
| 139 | + |
| 140 | +function _poly(a...) |
| 141 | + N = length(a) |
| 142 | + ex = :($(a[1])) |
| 143 | + for i in 2:N |
| 144 | + ex = :(muladd(x, $ex, $(a[i]))) |
| 145 | + end |
| 146 | + :(x -> $ex) |
| 147 | +end |
| 148 | + |
| 149 | +p = @poly 3 2 10 |
| 150 | +p(2) == evalpoly(2, [10,2,3]) |
| 151 | +@code_lowered p(2) # can show the generated code |
| 152 | +``` |
| 153 | + |
| 154 | +```@raw html |
| 155 | +</p></details> |
| 156 | +``` |
| 157 | + |
| 158 | + |
| 159 | +```@raw html |
| 160 | +<div class="admonition is-category-exercise"> |
| 161 | +<header class="admonition-header">Exercise</header> |
| 162 | +<div class="admonition-body"> |
| 163 | +``` |
| 164 | +Create macro `@poly` that takes two arguments (see [above](@ref lab07_polymacro)) and creates an anonymous function that constructs the unrolled code. |
| 165 | + |
| 166 | +**HINTS**: |
| 167 | +- though in general we should be prepared for some edge cases, assume that we are really strict with the syntax allowed |
| 168 | +- reuse `_poly` that we have defined in the previous exercise |
| 169 | +- use macro tools to match `a_*$v^(n_)`, where `v` is the symbol of independent variable |
| 170 | + + get maximal rank of the polynomial |
| 171 | + + get coefficient for each power |
| 172 | + |
| 173 | +!!! info "`MacroTools.jl`" |
| 174 | + Though not the most intuitive, `MacroTools.jl` pkg allows us to play with macros. |
| 175 | + - `@capture` |
| 176 | + - `postwalk`/`prewalk` |
| 177 | + **TODO** |
| 178 | + |
| 179 | +```@raw html |
| 180 | +</div></div> |
| 181 | +<details class = "solution-body"> |
| 182 | +<summary class = "solution-header">Solution:</summary><p> |
| 183 | +``` |
| 184 | + |
| 185 | +```julia |
| 186 | +using MacroTools |
| 187 | +using MacroTools: postwalk, prewalk |
| 188 | + |
| 189 | +macro poly(v::Symbol, p::Expr) |
| 190 | + a = Tuple(reverse(_get_coeffs(v, p))) |
| 191 | + return _poly(a...) |
| 192 | +end |
| 193 | + |
| 194 | +function _max_rank(v, p) |
| 195 | + mr = 0 |
| 196 | + postwalk(p) do x |
| 197 | + if @capture(x, a_*$v^(n_)) |
| 198 | + mr = max(mr, n) |
| 199 | + end |
| 200 | + x |
| 201 | + end |
| 202 | + mr |
| 203 | +end |
| 204 | + |
| 205 | +function _get_coeffs(v, p) |
| 206 | + N = _max_rank(v, p) + 1 |
| 207 | + coefficients = zeros(N) |
| 208 | + postwalk(p) do x |
| 209 | + if @capture(x, a_*$v^(n_)) |
| 210 | + coefficients[n+1] = a |
| 211 | + end |
| 212 | + x |
| 213 | + end |
| 214 | + coefficients |
| 215 | +end |
| 216 | + |
| 217 | +p = @poly x 3x^2 + 2x^1 + 10 |
| 218 | +p(2) == evalpoly(2, [10,2,3]) |
| 219 | +@code_lowered p(2) # can show the generated code |
| 220 | +``` |
| 221 | + |
| 222 | +```@raw html |
| 223 | +</p></details> |
| 224 | +``` |
| 225 | + |
| 226 | +## Ecosystem DSL |
| 227 | +### World definition |
| 228 | +### New Animal/Plant definition |
| 229 | + |
| 230 | +--- |
| 231 | +# Resources |
| 232 | +- macros in Julia [documentation](https://docs.julialang.org/en/v1/manual/metaprogramming/#man-macros) |
0 commit comments