Skip to content

Commit 0da1aa6

Browse files
committed
Lab7: added myshow-repeat-poly macros.
1 parent df2d0dc commit 0da1aa6

File tree

1 file changed

+232
-1
lines changed

1 file changed

+232
-1
lines changed

docs/src/lecture_07/lab.md

Lines changed: 232 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,232 @@
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

Comments
 (0)