Skip to content

Commit f612ee9

Browse files
committed
Lab7: Clarified exercises. Added hints.
Added links to the lecture.
1 parent d1ad491 commit f612ee9

File tree

1 file changed

+56
-19
lines changed

1 file changed

+56
-19
lines changed

docs/src/lecture_07/lab.md

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# [Lab 07: Macros](@id macro_lab)
2-
A little reminder from the lecture, a macro in its essence is a function, which
2+
A little reminder from the [lecture](@ref macro_lecture), a macro in its essence is a function, which
33
1. takes as an input an expression (parsed input)
44
2. modifies the expressions in arguments
55
3. inserts the modified expression at the same place as the one that is parsed.
66

77
In this lab we are going to use what we have learned about manipulation of expressions and explore avenues of where macros can be useful
8-
- convenience (`@repeat n code`, `@show`)
8+
- convenience (`@repeat`, `@show`)
99
- performance critical code generation (`@poly`)
1010
- alleviate tedious code generation (`@species`, `@eats`)
1111
- just as a syntactic sugar (`@ecosystem`)
@@ -43,10 +43,10 @@ Testing it gives us the expected behavior
4343
@myshow xx = 1 + 1
4444
xx # should be defined
4545
```
46-
In this "simple" example, we had to use the following concepts mentioned already in the lecture:
47-
- `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 - **TRUE QUOTING**
48-
- `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) - **ESCAPING**
49-
- `$(QuoteNode(ex))` and `$(esc(ex))` is used to evaluate an expression into another expression. **INTERPOLATION**
46+
In this "simple" example, we had to use the following concepts mentioned already in the [lecture](@ref macro_lecture):
47+
- `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 - [**TRUE QUOTING**](@ref lec7_quotation)
48+
- `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) - [**ESCAPING**](@ref lec7_hygiene)
49+
- `$(QuoteNode(ex))` and `$(esc(ex))` is used to evaluate an expression into another expression. [**INTERPOLATION**](@ref lec7_quotation)
5050
- `local value = ` is used in order to return back the result after evaluation
5151

5252
Lastly, let's mention that we can use `@macroexpand` to see how the code is manipulated in the `@myshow` macro
@@ -83,7 +83,9 @@ _repeat(3, :(println("Hello!"))) # testing "macro" without defining it
8383
```
8484

8585
**HINTS**:
86-
- use `$` interpolation into a for loop expression
86+
- use `$` interpolation into a for loop expression; for example given `ex = :(1+x)` we can interpolate it into another expression `:($ex + y)` -> `:(1 + x + y)`
87+
- if unsure what gets interpolated use round brackets `:($(ex) + y)`
88+
- macro is a function that *creates* code that does what we want
8789

8890
**BONUS**:
8991
What happens if we call `@repeat 3 x = 2`? Is `x` defined?
@@ -123,12 +125,14 @@ Ideally we would like write some macro `@poly` that takes a polynomial in a math
123125
*Example usage*:
124126
```julia
125127
p = @poly x 3x^2+2x^1+10x^0 # the first argument being the independent variable to match
128+
p(2) # return the value
126129
```
127130

128131
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.
129132

130133
```julia
131134
p = @poly 3 2 10
135+
p(2)
132136
```
133137

134138
```@raw html
@@ -138,10 +142,29 @@ p = @poly 3 2 10
138142
```
139143
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.
140144

145+
Recall Horner's method polynomial evaluation from previous [labs](@ref horner):
146+
```julia
147+
function polynomial(a, x)
148+
accumulator = a[end] * one(x)
149+
for i in length(a)-1:-1:1
150+
accumulator = accumulator * x + a[i]
151+
#= accumulator = muladd(x, accumulator, a[i]) =# # equivalent
152+
end
153+
accumulator
154+
end
155+
```
156+
141157
**HINTS**:
142158
- you can use `muladd` function as replacement for `ac * x + a[i]`
143-
- the expression should be built incrementally by nesting (try to write out the Horner's method[^1] to see it)
144-
- the order of coefficients has different order than in previous labs
159+
- think of the `accumulator` variable as the mathematical expression that is incrementally built (try to write out the Horner's method[^1] to see it)
160+
- you can nest expression arbitrarily
161+
- the order of coefficients has different order than in previous labs (going from )
162+
- use `evalpoly` to check the correctness
163+
```julia
164+
using Test
165+
p = @poly 3 2 10
166+
@test p(2) == evalpoly(2, [10,2,3]) # reversed coefficients
167+
```
145168

146169
[^1]: Explanation of the Horner schema can be found on [https://en.wikipedia.org/wiki/Horner%27s\_method](https://en.wikipedia.org/wiki/Horner%27s_method).
147170
```@raw html
@@ -151,6 +174,7 @@ Create macro `@poly` that takes multiple arguments and creates an anonymous func
151174
```
152175

153176
```@repl lab07_poly
177+
using InteractiveUtils #hide
154178
macro poly(a...)
155179
return _poly(a...)
156180
end
@@ -182,7 +206,7 @@ Moving on to the first/harder case, where we need to parse the mathematical expr
182206
```
183207
Create macro `@poly` that takes two arguments first one being the independent variable and second one being the polynomial written in mathematical notation. As in the previous case this macro should define an anonymous function that constructs the unrolled code.
184208
```julia
185-
julia> p = @poly x 3x^2 + 2x^1 + 10x^0 # the first argument being the independent variable to match
209+
julia> p = @poly x 3x^2+2x^1+10x^0 # the first argument being the independent variable to match
186210
```
187211

188212
**HINTS**:
@@ -262,7 +286,6 @@ end
262286
```
263287
Let's test it.
264288
```@repl lab07_poly
265-
using InteractiveUtils #hide
266289
p = @poly x 3x^2+2x^1+ 10
267290
p(2) == evalpoly(2, [10,2,3])
268291
@code_lowered p(2) # can show the generated code
@@ -357,6 +380,9 @@ Unfortunately the current version of `Ecosystem` and `EcosystemCore`, already co
357380
string.(hcat(["🌍", animal_species...], vcat(permutedims(species), em)))
358381
end
359382
eating_matrix()
383+
🌍 🐑 🐺 🌿 🍄
384+
🐑 ❌ ❌ ✅ ✅
385+
🐺 ✅ ❌ ❌ ❌
360386
```
361387

362388
```@raw html
@@ -369,14 +395,24 @@ Based on the following example syntax,
369395
@species Plant Broccoli 🥦
370396
@species Animal Rabbit 🐇
371397
```
372-
write macro `@species` inside `Ecosystem` pkg, which defines the abstract type, its show function and exports the type.
373-
374-
Define first helper function `_species` to inspect the macro's output. This is indispensable, as we are defining new types/constants and thus we would otherwise encountered errors during repeated evaluation (though only if the type signature changed).
398+
write macro `@species` inside `Ecosystem` pkg, which defines the abstract type, its show function and exports the type. For example `@species Plant Broccoli 🥦` should generate code:
399+
```julia
400+
abstract type Broccoli <: PlantSpecies end
401+
Base.show(io::IO,::Type{Broccoli}) = print(io,"🥦")
402+
export Broccoli
403+
```
404+
Define first helper function `_species` to inspect the macro's output. This is indispensable, as we are defining new types/constants and thus we may otherwise encounter errors during repeated evaluation (though only if the type signature changed).
405+
```julia
406+
_species(:Plant, :Broccoli, :🥦)
407+
_species(:Animal, :Rabbit, :🐇)
408+
```
375409

376410
**HINTS**:
377411
- use `QuoteNode` in the show function just like in the `@myshow` example
378-
- ideally these changes should be made inside the modified `Ecosystem` pkg provided in the lab (though not everything can be refreshed with `Revise`)
412+
- escaping `esc` is needed for the returned in order to evaluate in the top most module (`Ecosystem`/`Main`)
413+
- ideally these changes should be made inside the modified `Ecosystem` pkg provided in the lab (though not everything can be refreshed with `Revise`) - there is a file `ecosystem_macros.jl` just for this purpose
379414
- multiple function definitions can be included into a `quote end` block
415+
- interpolation works with any expression, e.g. `$(typ == :Animal ? AnimalSpecies : PlantSpecies)`
380416

381417
**BONUS**:
382418
Based on `@species` define also macros `@animal` and `@plant` with two arguments instead of three, where the species type is implicitly carried in the macro's name.
@@ -432,8 +468,9 @@ Define macro `@eats` inside `Ecosystem` pkg that assigns particular species thei
432468
where `Grass => 0.5` defines the behavior of the `eat!` function. The coefficient is used here as a multiplier for the energy balance, in other words the `Rabbit` should get only `0.5` of energy for a piece of `Grass`.
433469

434470
**HINTS**:
435-
- ideally these changes should be made inside the modified `Ecosystem` pkg provided in the lab (though not everything can be refreshed with `Revise`)
436-
- you can create an empty `quote end` block with `code = Expr(:block)` and push new expressions incrementally
471+
- ideally these changes should be made inside the modified `Ecosystem` pkg provided in the lab (though not everything can be refreshed with `Revise`) - there is a file `ecosystem_macros.jl` just for this purpose
472+
- escaping `esc` is needed for the returned in order to evaluate in the top most module (`Ecosystem`/`Main`)
473+
- you can create an empty `quote end` block with `code = Expr(:block)` and push new expressions into its `args` incrementally
437474
- use dispatch to create specific code for the different combinations of agents eating other agents (there may be catch in that we have to first `eval` the symbols before calling in order to know if they are animals or plants)
438475

439476
!!! note "Reminder of `EcosystemCore` `eat!` and `eats` functionality"
@@ -512,10 +549,10 @@ _eats(species, foodlist)
512549
```
513550

514551
---
515-
# Resources
552+
## Resources
516553
- macros in Julia [documentation](https://docs.julialang.org/en/v1/manual/metaprogramming/#man-macros)
517554

518-
## `Type{T}` type selectors
555+
### `Type{T}` type selectors
519556
We have used `::Type{T}` signature[^2] at few places in the `Ecosystem` family of packages (and it will be helpful in the HW as well), such as in the `show` methods
520557
```julia
521558
Base.show(io::IO,::Type{World}) = print(io,"🌍")

0 commit comments

Comments
 (0)