Skip to content

Commit a567419

Browse files
committed
2 parents e947459 + effe481 commit a567419

File tree

2 files changed

+92
-91
lines changed

2 files changed

+92
-91
lines changed

docs/src/lectures/lecture_06/lecture.md

Lines changed: 49 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -318,17 +318,19 @@ and the output is used mainly for debugging / inspection.
318318
Language introspection is very convenient for investigating, how things are implemented and how they are optimized / compiled to the native code.
319319

320320
::: note "Reminder `@which`"
321-
Though we have already used it quite a few times, recall the very useful macro `@which`, which identifies the concrete function called in a function call. For example `@which mapreduce(sin, +, [1,2,3,4])`. Note again that the macro here is a convenience macro to obtain types of arguments from the expression. Under the hood, it calls `InteractiveUtils.which(function_name, (Base.typesof)(args...))`. Funny enough, you can call `@which InteractiveUtils.which(+, (Base.typesof)(1,1))` to inspect, where `which` is defined.
321+
Though we have already used it quite a few times, recall the very useful macro `@which`, which identifies the concrete function called in a function call. For example `@which mapreduce(sin, +, [1,2,3,4])`. Note again that the macro here is a convenience macro to obtain types of arguments from the expression. Under the hood, it calls `InteractiveUtils.which(function_name, (Base.typesof)(args...))`. Funny enough, you can call `@which InteractiveUtils.which(+, (Base.typesof)(1,1))` to inspect, where `which` is defined.
322322

323-
Alternatively, you can invest time in learning `Cthulhu.jl` package, which is a tool for inspecting functions called in a function. In other words it will simplify a recursive call of which when one is interested in how internals of some function are implemented.
323+
Alternatively, you can invest time in learning `Cthulhu.jl` package, which is a tool for inspecting functions called in a function. In other words it will simplify a recursive call of which when one is interested in how internals of some function are implemented.
324+
:::
324325

325326
::: note "Effect analysis"
326-
The compiler is analysing the code to automatically infer some properties, which can help it to create more efficient code. This is analysis id called effect analysis and you can execute it yourself using `Base.infer_effects`. For example for our `nextfib` we obtain (on 1.11.1)
327-
```julia
328-
julia> Base.infer_effects(nextfib, (Int,))
329-
(+c,+e,+n,!t,+s,+m,+u,+o,+r)
330-
```
331-
See the documentation for (Base.@assume_effects)[https://docs.julialang.org/en/v1/base/base/#Base.@assume_effects] for details of individual fields.
327+
The compiler is analysing the code to automatically infer some properties, which can help it to create more efficient code. This is analysis id called effect analysis and you can execute it yourself using `Base.infer_effects`. For example for our `nextfib` we obtain (on 1.11.1)
328+
```julia
329+
julia> Base.infer_effects(nextfib, (Int,))
330+
(+c,+e,+n,!t,+s,+m,+u,+o,+r)
331+
```
332+
See the documentation for (Base.@assume_effects)[https://docs.julialang.org/en/v1/base/base/#Base.@assume_effects] for details of individual fields.
333+
:::
332334

333335
### Broadcasting
334336
Broadcasting is not a unique concept in programming languages (Python/Numpy, MATLAB), however its implementation in Julia allows to easily fuse operations. For example
@@ -399,9 +401,9 @@ unstable_pack = [Wolf("1", 1), Wolf("2", 2), Sheep("3", 3)]
399401
@code_typed map(sound, stable_pack)
400402
@code_typed map(sound, unstable_pack)
401403
```
402-
::: info
403-
## Cthulhu.jl
404-
`Cthulhu.jl` is a library (tool) which simplifies the above, where we want to iteratively dive into functions called in some piece of code (typically some function). `Cthulhu` is different from te normal debugger, since the debugger is executing the code, while `Cthulhu` is just lower_typing the code and presenting functions (with type of arguments inferred).
404+
::: info Cthulhu.jl
405+
`Cthulhu.jl` is a library (tool) which simplifies the above, where we want to iteratively dive into functions called in some piece of code (typically some function). `Cthulhu` is different from te normal debugger, since the debugger is executing the code, while `Cthulhu` is just lower_typing the code and presenting functions (with type of arguments inferred).
406+
:::
405407
406408
```julia
407409
using Cthulhu
@@ -468,57 +470,59 @@ The parsed code `p` is of type `Expr`, which according to Julia's help[^2] is *a
468470
[^2]: Help: [`Core.Expr`](https://docs.julialang.org/en/v1/base/base/#Core.Expr)
469471
470472
::: info "Symbol type"
471-
When manipulations of expressions, we encounter the term `Symbol`. `Symbol` is the smallest atom from which the program (in AST representation) is built. It is used to identify an element in the language, for example variable, keyword or function name. Symbol is not a string, since string represents itself, whereas `Symbol` can represent something else (a variable). An illustrative example[^3] goes as follows.
472-
```julia
473-
julia> eval(:foo)
474-
ERROR: foo not defined
473+
When manipulations of expressions, we encounter the term `Symbol`. `Symbol` is the smallest atom from which the program (in AST representation) is built. It is used to identify an element in the language, for example variable, keyword or function name. Symbol is not a string, since string represents itself, whereas `Symbol` can represent something else (a variable). An illustrative example[^3] goes as follows.
474+
```julia
475+
julia> eval(:foo)
476+
ERROR: foo not defined
475477
476-
julia> foo = "hello"
477-
"hello"
478+
julia> foo = "hello"
479+
"hello"
478480
479-
julia> eval(:foo)
480-
"hello"
481+
julia> eval(:foo)
482+
"hello"
481483
482-
julia> eval("foo")
483-
"foo"
484-
```
485-
which shows that what the symbol `:foo` evaluates to depends on what – if anything – the variable `foo` is bound to, whereas "foo" always just evaluates to "foo".
484+
julia> eval("foo")
485+
"foo"
486+
```
487+
which shows that what the symbol `:foo` evaluates to depends on what – if anything – the variable `foo` is bound to, whereas "foo" always just evaluates to "foo".
486488
487-
Symbols can be constructed either by prepending any string with `:` or by calling `Symbol(...)`, which concatenates the arguments and create the symbol out of it. All of the following are symbols
488-
```julia
489-
julia> :+
490-
:+
489+
Symbols can be constructed either by prepending any string with `:` or by calling `Symbol(...)`, which concatenates the arguments and create the symbol out of it. All of the following are symbols
490+
```julia
491+
julia> :+
492+
:+
491493
492-
julia> :function
493-
:function
494+
julia> :function
495+
:function
494496
495-
julia> :call
496-
:call
497+
julia> :call
498+
:call
497499
498-
julia> :x
499-
:x
500+
julia> :x
501+
:x
500502
501-
julia> Symbol(:Very,"_twisted_",:symbol,"_definition")
502-
:Very_twisted_symbol_definition
503+
julia> Symbol(:Very,"_twisted_",:symbol,"_definition")
504+
:Very_twisted_symbol_definition
503505
504-
julia> Symbol("Symbol with blanks")
505-
Symbol("Symbol with blanks")
506-
```
507-
Symbols therefore allows us to operate with a piece of code without evaluating it.
506+
julia> Symbol("Symbol with blanks")
507+
Symbol("Symbol with blanks")
508+
```
509+
Symbols therefore allows us to operate with a piece of code without evaluating it.
508510
509-
In Julia, symbols are "interned strings", which means that compiler attaches each string a unique identifier (integer), such that it can quickly compare them. Compiler uses Symbols exclusively and the important feature is that they can be quickly compared. This is why people like to use them as keys in `Dict`.
511+
In Julia, symbols are "interned strings", which means that compiler attaches each string a unique identifier (integer), such that it can quickly compare them. Compiler uses Symbols exclusively and the important feature is that they can be quickly compared. This is why people like to use them as keys in `Dict`.
512+
:::
510513
511514
[^3]: An [example](https://stackoverflow.com/questions/23480722/what-is-a-symbol-in-julia) provided by Stefan Karpinski.
512515
513516
::: info "Expressions"
514-
From Julia's help[^2]:
517+
From Julia's help[^2]:
515518
516-
`Expr(head::Symbol, args...)`
519+
`Expr(head::Symbol, args...)`
517520
518-
A type representing compound expressions in parsed julia code (ASTs). Each expression consists of a head `Symbol` identifying which kind of expression it is (e.g. a call, for loop, conditional statement, etc.), and subexpressions (e.g. the arguments of a call).
519-
The subexpressions are stored in a `Vector{Any}` field called args.
521+
A type representing compound expressions in parsed julia code (ASTs). Each expression consists of a head `Symbol` identifying which kind of expression it is (e.g. a call, for loop, conditional statement, etc.), and subexpressions (e.g. the arguments of a call).
522+
The subexpressions are stored in a `Vector{Any}` field called args.
520523
521-
The expression is simple yet very flexible. The head `Symbol` tells how the expression should be treated and arguments provide all needed parameters. Notice that the structure is also type-unstable. This is not a big deal, since the expression is used to generate code, hence it is not executed repeatedly.
524+
The expression is simple yet very flexible. The head `Symbol` tells how the expression should be treated and arguments provide all needed parameters. Notice that the structure is also type-unstable. This is not a big deal, since the expression is used to generate code, hence it is not executed repeatedly.
525+
:::
522526
523527
## Construct code from scratch
524528
Since `Expr` is a Julia structure, we can construct it manually as we can construct any other structure

docs/src/lectures/lecture_07/lecture.md

Lines changed: 43 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -74,22 +74,21 @@ CodeInfo(
7474
)
7575
```
7676

77-
::: info
78-
### Scope of eval
79-
`eval` function is always evaluated in the global scope of the `Module` in which the macro is called (note that there is that by default you operate in the `Main` module). Moreover, `eval` takes effect **after** the function has been has been executed. This can be demonstrated as
80-
```julia
81-
add1(x) = x + 1
82-
function redefine_add(x)
83-
eval(:(add1(x) = x - 1))
84-
add1(x)
85-
end
86-
julia> redefine_add(1)
87-
2
88-
89-
julia> redefine_add(1)
90-
0
91-
92-
```
77+
::: info Scope of eval
78+
`eval` function is always evaluated in the global scope of the `Module` in which it is called (note that there is that by default you operate in the `Main` module). Moreover, `eval` takes effect **after** the function has been has been executed. This can be demonstrated as
79+
```julia
80+
add1(x) = x + 1
81+
function redefine_add(x)
82+
eval(:(add1(x) = x - 1))
83+
add1(x)
84+
end
85+
julia> redefine_add(1)
86+
2
87+
88+
julia> redefine_add(1)
89+
0
90+
```
91+
:::
9392

9493
Macros are quite tricky to debug. Macro `@macroexpand` allows to observe the expansion of macros. Observe the effect as
9594
```julia
@@ -103,7 +102,6 @@ function cosp2(x)
103102
@replace_sin 2 + sin(x)
104103
end
105104
```
106-
107105
First, Julia parses the code into the AST as
108106
```julia
109107
ex = Meta.parse("""
@@ -161,7 +159,6 @@ end
161159
```
162160
(the `@showarg(1 + 1, :x)` raises an error, since `:(:x)` is of Type `QuoteNode`).
163161

164-
165162
Observe that macro dispatch is based on the types of AST that are handed to the macro, not the types that the AST evaluates to at runtime.
166163

167164
List of all defined versions of macro
@@ -228,44 +225,44 @@ end
228225
The error code snippet errors telling us that the expression `"$"` is outside of a quote block. This is because the macro `@no_quote` has returned a block with `$` occuring outside of `quote` or string definition.
229226

230227
::: info
231-
Some macros like `@eval` (recall last example)
232-
```julia
233-
for f in [:setindex!, :getindex, :size, :length]
234-
@eval $(f)(A::MyMatrix, args...) = $(f)(A.x, args...)
235-
end
236-
```
237-
or `@benchmark` support interpolation of values. This interpolation needs to be handled by the logic of the macro and is not automatically handled by Julia language.
228+
Some macros like `@eval` (recall last example)
229+
```julia
230+
for f in [:setindex!, :getindex, :size, :length]
231+
@eval $(f)(A::MyMatrix, args...) = $(f)(A.x, args...)
232+
end
233+
```
234+
or `@benchmark` support interpolation of values. This interpolation needs to be handled by the logic of the macro and is not automatically handled by Julia language.
235+
:::
238236

239237
Macros do not know about runtime values, they only know about syntax trees. When a macro receives an expression with a `$x` in it, it can't interpolate the value of x into the syntax tree because it reads the syntax tree before `x` ever has a value!
240238

241239
Instead, when a macro is given an expression with `$` in it, it assumes you're going to give your own meaning to `$x`. In the case of BenchmarkTools.jl they return code that has to wait until runtime to receive the value of `x` and then splice that value into an expression which is evaluated and benchmarked. Nowhere in the actual body of the macro do they have access to the value of `x` though.
242240

243241

244-
::: info
245-
### Why `$` for interpolation?
246-
The `$` string for interpolation was used as it identifies the interpolation inside the string and inside the command. For example
247-
```julia
248-
a = 5
249-
s = "a = $(a)"
250-
typoef(s)
251-
println(s)
252-
filename = "/tmp/test_of_interpolation"
253-
run(`touch $(filename)`)
254-
```
242+
::: info Why `$` for interpolation?
243+
The `$` string for interpolation was used as it identifies the interpolation inside the string and inside the command. For example
244+
```julia
245+
a = 5
246+
s = "a = $(a)"
247+
typoef(s)
248+
println(s)
249+
filename = "/tmp/test_of_interpolation"
250+
run(`touch $(filename)`)
251+
```
252+
:::
255253

256-
## [Macro hygiene](@id lec7_hygiene)
254+
## Macro hygiene
257255
Macro hygiene is a term coined in 1986 addressing the following problem: if you're automatically generating code, it's possible that you will introduce variable names in your generated code that will clash with existing variable names in the scope in which a macro is called. These clashes might cause your generated code to read from or write to variables that you should not be interacting with. A macro is hygienic when it does not interact with existing variables, which means that when macro is evaluated, it should not have any effect on the surrounding code.
258256

259257
By default, all macros in Julia are hygienic which means that variables introduced in the macro have automatically generated names, where Julia ensures they will not collide with user's variable. These variables are created by `gensym` function / macro.
260258

261-
::: info
262-
### gensym
263-
264-
`gensym([tag])` Generates a symbol which will not conflict with other variable names.
265-
```julia
266-
julia> gensym("hello")
267-
Symbol("##hello#257")
268-
```
259+
::: info gensym
260+
`gensym([tag])` Generates a symbol which will not conflict with other variable names.
261+
```julia
262+
julia> gensym("hello")
263+
Symbol("##hello#257")
264+
```
265+
:::
269266

270267
Let's demonstrate it on our own version of an macro `@elapsed` which will return the time that was needed to evaluate the block of code.
271268
```julia
@@ -477,7 +474,7 @@ module Exfiltrator
477474
const environment = Dict{Symbol, Any}()
478475

479476
function copy_variables!(d::Dict)
480-
foreach(k -> delete!(environment, k), keys(environment))
477+
empty!(environment)
481478
for (k, v) in d
482479
environment[k] = v
483480
end

0 commit comments

Comments
 (0)