generated from JuliaPluto/static-export-template
-
Notifications
You must be signed in to change notification settings - Fork 0
/
metaprogramming.jl
1924 lines (1590 loc) · 75.4 KB
/
metaprogramming.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
### A Pluto.jl notebook ###
# v0.14.1
using Markdown
using InteractiveUtils
# ╔═╡ 4a329a4e-1fe3-431d-98ec-8161764a3ee1
md"""
# Metaprogramming
"""
# ╔═╡ fcf7fb90-a229-4a34-af4d-e1da308a1e78
md"""
The strongest legacy of Lisp in the Julia language is its metaprogramming support. Like Lisp, Julia represents its own code as a data structure of the language itself. Since code is represented by objects that can be created and manipulated from within the language, it is possible for a program to transform and generate its own code. This allows sophisticated code generation without extra build steps, and also allows true Lisp-style macros operating at the level of [abstract syntax trees](https://en.wikipedia.org/wiki/Abstract_syntax_tree). In contrast, preprocessor \"macro\" systems, like that of C and C++, perform textual manipulation and substitution before any actual parsing or interpretation occurs. Because all data types and code in Julia are represented by Julia data structures, powerful [reflection](https://en.wikipedia.org/wiki/Reflection_%28computer_programming%29) capabilities are available to explore the internals of a program and its types just like any other data.
"""
# ╔═╡ 7e910758-9b9d-4eb0-a161-f1b7eac39129
md"""
## Program representation
"""
# ╔═╡ 0167e817-d8d3-4c51-87fe-9bf0619dcff2
md"""
Every Julia program starts life as a string:
"""
# ╔═╡ 36097fc2-d38d-4a6f-9c59-5942f2758803
prog = "1 + 1"
# ╔═╡ 11d110f1-7629-4af9-b3d9-703dcd1b387d
md"""
**What happens next?**
"""
# ╔═╡ 96b8f192-6774-4dde-87a2-64a81f8dbf1f
md"""
The next step is to [parse](https://en.wikipedia.org/wiki/Parsing#Computer_languages) each string into an object called an expression, represented by the Julia type [`Expr`](@ref):
"""
# ╔═╡ 8fcfafa3-f229-44c9-a431-2214b02726d0
ex1 = Meta.parse(prog)
# ╔═╡ 7859f9f6-1443-46db-9657-9f1615e279d6
typeof(ex1)
# ╔═╡ 15a2df63-e2ff-46df-9369-baad4e4dc748
md"""
`Expr` objects contain two parts:
"""
# ╔═╡ 54613218-df4b-4419-a139-cbfc03600cee
md"""
* a [`Symbol`](@ref) identifying the kind of expression. A symbol is an [interned string](https://en.wikipedia.org/wiki/String_interning) identifier (more discussion below).
"""
# ╔═╡ 36ef56a5-6220-4626-92d5-bee76a2328f7
ex1.head
# ╔═╡ 55904141-a6a2-4cb4-b062-73783a1f5cbf
md"""
* the expression arguments, which may be symbols, other expressions, or literal values:
"""
# ╔═╡ 4e1a9122-0c7f-4f28-93f5-656daf84ecf7
ex1.args
# ╔═╡ e7318bff-ae20-4ddc-b0d9-2ad950123f1a
md"""
Expressions may also be constructed directly in [prefix notation](https://en.wikipedia.org/wiki/Polish_notation):
"""
# ╔═╡ 33ee25b5-147f-40ec-a245-10752bdf73d5
ex2 = Expr(:call, :+, 1, 1)
# ╔═╡ 6187fc22-7e83-4b7d-876f-e1b18f597a56
md"""
The two expressions constructed above – by parsing and by direct construction – are equivalent:
"""
# ╔═╡ f3db3506-1417-4d5c-9d3c-550ce11b12a2
ex1 == ex2
# ╔═╡ d5505c6f-8897-467b-aec3-ac106f7d2a81
md"""
**The key point here is that Julia code is internally represented as a data structure that is accessible from the language itself.**
"""
# ╔═╡ da20398b-0959-4024-be1a-cd336f6fd596
md"""
The [`dump`](@ref) function provides indented and annotated display of `Expr` objects:
"""
# ╔═╡ 208960e1-6204-4df6-93e3-5586c8111e54
dump(ex2)
# ╔═╡ 0f99abc3-4700-4846-8128-572e88c1c426
md"""
`Expr` objects may also be nested:
"""
# ╔═╡ 1c3802e8-d7e9-4454-9729-dd108c1fdf8f
ex3 = Meta.parse("(4 + 4) / 2")
# ╔═╡ 620275f6-5e71-4a48-8df8-cc8e6e3d91e9
md"""
Another way to view expressions is with `Meta.show_sexpr`, which displays the [S-expression](https://en.wikipedia.org/wiki/S-expression) form of a given `Expr`, which may look very familiar to users of Lisp. Here's an example illustrating the display on a nested `Expr`:
"""
# ╔═╡ fefeb34f-3d1e-4b18-bb37-26f7a08de741
Meta.show_sexpr(ex3)
# ╔═╡ 5a251167-1ca2-4311-800c-8a65fd8bce27
md"""
### Symbols
"""
# ╔═╡ c3875772-7266-475c-82f0-f574df3e00f9
md"""
The `:` character has two syntactic purposes in Julia. The first form creates a [`Symbol`](@ref), an [interned string](https://en.wikipedia.org/wiki/String_interning) used as one building-block of expressions:
"""
# ╔═╡ 53c5a159-79ad-4fbb-be77-28d9f5ce355a
s = :foo
# ╔═╡ 0ed50b34-bf20-4cae-abbf-a23bb2a6b3e1
typeof(s)
# ╔═╡ 77270b5d-db24-4034-9acb-df32bfc0046e
md"""
The [`Symbol`](@ref) constructor takes any number of arguments and creates a new symbol by concatenating their string representations together:
"""
# ╔═╡ b8e25ef0-31e4-45ca-8641-94a722dcd04c
:foo == Symbol("foo")
# ╔═╡ 37039812-4c1a-4a83-b226-de3e3bb46708
Symbol("func",10)
# ╔═╡ 55b7d0c1-2313-4aab-a91d-3f8df3aad0f8
Symbol(:var,'_',"sym")
# ╔═╡ d69d7939-28af-4d27-8160-5ce95352d0f5
md"""
Note that to use `:` syntax, the symbol's name must be a valid identifier. Otherwise the `Symbol(str)` constructor must be used.
"""
# ╔═╡ 80d66efe-4dfd-4b2b-9c8f-086d89c0f57c
md"""
In the context of an expression, symbols are used to indicate access to variables; when an expression is evaluated, a symbol is replaced with the value bound to that symbol in the appropriate [scope](@ref scope-of-variables).
"""
# ╔═╡ 3ad47a4e-c1a0-40f6-b060-6e81ba4b5fce
md"""
Sometimes extra parentheses around the argument to `:` are needed to avoid ambiguity in parsing:
"""
# ╔═╡ 0d2d15b7-a8db-409c-b583-55ad3b31f7b7
:(:)
# ╔═╡ 89162a72-fc8a-4ae8-b879-92257bfab63f
:(::)
# ╔═╡ bb530cb9-1ca7-4c97-8d16-18390d77ceb9
md"""
## Expressions and evaluation
"""
# ╔═╡ bd81c653-8361-40b8-9ce7-5996a124fe67
md"""
### Quoting
"""
# ╔═╡ 7aee02ac-e84f-4e07-9fd6-29864de59dfd
md"""
The second syntactic purpose of the `:` character is to create expression objects without using the explicit [`Expr`](@ref) constructor. This is referred to as *quoting*. The `:` character, followed by paired parentheses around a single statement of Julia code, produces an `Expr` object based on the enclosed code. Here is example of the short form used to quote an arithmetic expression:
"""
# ╔═╡ 1018af97-73a3-4e9e-9e7e-a2249a37abb9
ex = :(a+b*c+1)
# ╔═╡ e8db5d80-7350-40e3-812d-42fa5293abb0
typeof(ex)
# ╔═╡ ceeea500-f6c8-4743-9d22-9b72c7da7ed7
md"""
(to view the structure of this expression, try `ex.head` and `ex.args`, or use [`dump`](@ref) as above or [`Meta.@dump`](@ref))
"""
# ╔═╡ 37827ba6-65ab-4dd9-82f5-310cb30a0712
md"""
Note that equivalent expressions may be constructed using [`Meta.parse`](@ref) or the direct `Expr` form:
"""
# ╔═╡ 5a6edb10-f59a-4874-8eaa-a52eb8991771
:(a + b*c + 1) ==
Meta.parse("a + b*c + 1") ==
Expr(:call, :+, :a, Expr(:call, :*, :b, :c), 1)
# ╔═╡ 52404149-600f-48f8-9e8d-5e8c9b0b0a04
md"""
Expressions provided by the parser generally only have symbols, other expressions, and literal values as their args, whereas expressions constructed by Julia code can have arbitrary run-time values without literal forms as args. In this specific example, `+` and `a` are symbols, `*(b,c)` is a subexpression, and `1` is a literal 64-bit signed integer.
"""
# ╔═╡ 3fcb66c2-4e66-4f45-a7e7-65036efca00a
md"""
There is a second syntactic form of quoting for multiple expressions: blocks of code enclosed in `quote ... end`.
"""
# ╔═╡ d958acef-73fe-4109-9967-24e5f4ca569f
ex = quote
x = 1
y = 2
x + y
end
# ╔═╡ ba42645b-fb45-4221-8d40-9840cb02264c
typeof(ex)
# ╔═╡ 2128a38f-7b95-449c-b561-06268ddc755b
md"""
### [Interpolation](@id man-expression-interpolation)
"""
# ╔═╡ 03d378b5-4419-41e2-91e9-cd0ba96e3f0c
md"""
Direct construction of [`Expr`](@ref) objects with value arguments is powerful, but `Expr` constructors can be tedious compared to \"normal\" Julia syntax. As an alternative, Julia allows *interpolation* of literals or expressions into quoted expressions. Interpolation is indicated by a prefix `$`.
"""
# ╔═╡ 44e77b14-6cab-46fb-adb7-67e75dc0857b
md"""
In this example, the value of variable `a` is interpolated:
"""
# ╔═╡ 63ff3614-7d0f-4c5e-9bb5-49170e5aa724
a = 1;
# ╔═╡ 4d55b495-251d-4306-a7de-96517a0f27db
ex = :($a + b)
# ╔═╡ 38ceea8c-8c47-40eb-93bd-f1b4d2fb1dd7
md"""
Interpolating into an unquoted expression is not supported and will cause a compile-time error:
"""
# ╔═╡ d9435823-a23b-4a48-9b20-a8a60cbd5642
$a + b
# ╔═╡ 452953e8-0b5e-4ec1-b186-920c3c3a6a92
md"""
In this example, the tuple `(1,2,3)` is interpolated as an expression into a conditional test:
"""
# ╔═╡ 4252a8c4-fbc1-478e-9853-bd14ae34028b
ex = :(a in $:((1,2,3)) )
# ╔═╡ ade9fd13-8cae-4fa4-9177-4d4f95e648fa
md"""
The use of `$` for expression interpolation is intentionally reminiscent of [string interpolation](@ref string-interpolation) and [command interpolation](@ref command-interpolation). Expression interpolation allows convenient, readable programmatic construction of complex Julia expressions.
"""
# ╔═╡ a3abc7e4-82dc-44f9-a36d-2bb887485366
md"""
### Splatting interpolation
"""
# ╔═╡ 43cc1bf6-bb71-4283-bcac-9f1e5beb6320
md"""
Notice that the `$` interpolation syntax allows inserting only a single expression into an enclosing expression. Occasionally, you have an array of expressions and need them all to become arguments of the surrounding expression. This can be done with the syntax `$(xs...)`. For example, the following code generates a function call where the number of arguments is determined programmatically:
"""
# ╔═╡ ee9cdf5f-fbe3-4739-9a0f-0807a71832e1
args = [:x, :y, :z];
# ╔═╡ 8c80e767-c31e-4b90-a796-3bd172807b99
:(f(1, $(args...)))
# ╔═╡ e9060435-8f4d-4d3b-a712-6d4c996ed5a5
md"""
### Nested quote
"""
# ╔═╡ 159f3cb2-3db5-4353-a856-653feacb5e92
md"""
Naturally, it is possible for quote expressions to contain other quote expressions. Understanding how interpolation works in these cases can be a bit tricky. Consider this example:
"""
# ╔═╡ b9ad3232-5587-4a03-89cf-98538cf6768e
x = :(1 + 2);
# ╔═╡ 42e7f30a-da9e-431a-8f3d-6a86c37a42a8
e = quote quote $x end end
# ╔═╡ f3eee563-1bca-49e9-892a-02f0f7f383fb
md"""
Notice that the result contains `$x`, which means that `x` has not been evaluated yet. In other words, the `$` expression \"belongs to\" the inner quote expression, and so its argument is only evaluated when the inner quote expression is:
"""
# ╔═╡ c4955aff-fe45-4f25-ba4a-76cf37cbf313
eval(e)
# ╔═╡ 8f3c873c-ea0a-476b-b976-c4952d0862c4
md"""
However, the outer `quote` expression is able to interpolate values inside the `$` in the inner quote. This is done with multiple `$`s:
"""
# ╔═╡ ccf86fe2-36fb-4cc4-9896-f57908929057
e = quote quote $$x end end
# ╔═╡ f1f4f452-3a8b-460d-90c4-a8051081baa5
md"""
Notice that `(1 + 2)` now appears in the result instead of the symbol `x`. Evaluating this expression yields an interpolated `3`:
"""
# ╔═╡ 9eb4d9b8-8d0e-45aa-918f-a4c15f0fc7dd
eval(e)
# ╔═╡ 6eb1be2c-1380-40c4-9ad4-97df4bd0c00e
md"""
The intuition behind this behavior is that `x` is evaluated once for each `$`: one `$` works similarly to `eval(:x)`, giving `x`'s value, while two `$`s do the equivalent of `eval(eval(:x))`.
"""
# ╔═╡ e2fc1d1c-8926-4d6d-b98c-272513971354
md"""
### [QuoteNode](@id man-quote-node)
"""
# ╔═╡ be25c407-2da0-4a73-8d61-f397809c6a0d
md"""
The usual representation of a `quote` form in an AST is an [`Expr`](@ref) with head `:quote`:
"""
# ╔═╡ 555a7c0a-19b9-4fa5-bfed-18e87b944a08
dump(Meta.parse(":(1+2)"))
# ╔═╡ 613595ed-d306-4c9b-a02c-002d8adbfc7c
md"""
As we have seen, such expressions support interpolation with `$`. However, in some situations it is necessary to quote code *without* performing interpolation. This kind of quoting does not yet have syntax, but is represented internally as an object of type `QuoteNode`:
"""
# ╔═╡ f7e81888-47e6-46fc-8328-1fb859e60541
eval(Meta.quot(Expr(:$, :(1+2))))
# ╔═╡ 86cf0594-0cb9-4385-9eac-931ef2ec7230
eval(QuoteNode(Expr(:$, :(1+2))))
# ╔═╡ ee702c43-2fb0-4772-858d-cf8c914a82bf
md"""
The parser yields `QuoteNode`s for simple quoted items like symbols:
"""
# ╔═╡ 56964666-aaa8-4e5b-9032-27899e9e9021
dump(Meta.parse(":x"))
# ╔═╡ 92b11cbe-9bd5-4ffa-b847-1e49bc399c64
md"""
`QuoteNode` can also be used for certain advanced metaprogramming tasks.
"""
# ╔═╡ bdd51c19-3ac2-42ee-bce2-cc63d25c45f1
md"""
### Evaluating expressions
"""
# ╔═╡ 8c70bcc3-1f62-43c2-875a-10e0349f6ff7
md"""
Given an expression object, one can cause Julia to evaluate (execute) it at global scope using [`eval`](@ref):
"""
# ╔═╡ 1bc594e5-f04a-41de-8ae0-83d5e0a0b1c9
ex1 = :(1 + 2)
# ╔═╡ e21a813e-1f35-4e61-9d24-dfe075e20bfa
eval(ex1)
# ╔═╡ 6537044b-c279-4e5b-8b8d-aeece11678de
ex = :(a + b)
# ╔═╡ 0a5e1a3e-9613-4972-b8e5-eebd16fc2538
eval(ex)
# ╔═╡ 633972c2-e96d-482e-8a36-55b090d6f31e
a = 1; b = 2;
# ╔═╡ 094811c3-f379-482c-8e2b-2e31d2b2dae6
eval(ex)
# ╔═╡ 1810881a-502d-44d0-9f25-e179b0a3fdc3
md"""
Every [module](@ref modules) has its own [`eval`](@ref) function that evaluates expressions in its global scope. Expressions passed to [`eval`](@ref) are not limited to returning values – they can also have side-effects that alter the state of the enclosing module's environment:
"""
# ╔═╡ b4d7164c-5ebc-4e03-8750-d4303d5040fc
ex = :(x = 1)
# ╔═╡ 9174d44b-3cb8-481d-8625-89a34974fc5b
x
# ╔═╡ 4f685b00-8535-462a-b017-342a94f38c37
eval(ex)
# ╔═╡ 408fbb67-959f-4a68-a8b3-1630f41d24d1
x
# ╔═╡ ac140b00-a8c3-4e81-8d83-b65155d1f8a0
md"""
Here, the evaluation of an expression object causes a value to be assigned to the global variable `x`.
"""
# ╔═╡ 5511e05a-be65-4295-b1ff-b1204bbf6e8a
md"""
Since expressions are just `Expr` objects which can be constructed programmatically and then evaluated, it is possible to dynamically generate arbitrary code which can then be run using [`eval`](@ref). Here is a simple example:
"""
# ╔═╡ af23182f-10e9-4dfb-ac33-6e161a2f4889
a = 1;
# ╔═╡ 48f159ac-073c-4b4e-bdac-f6d65d24ef6d
ex = Expr(:call, :+, a, :b)
# ╔═╡ d4f5c5d2-b6af-4d6c-b1c1-729580dbb94f
a = 0; b = 2;
# ╔═╡ 590c8581-b7d5-4cb4-acda-b1ceafbd113b
eval(ex)
# ╔═╡ 7655bf82-840f-48e0-a0ba-fc2063bc2307
md"""
The value of `a` is used to construct the expression `ex` which applies the `+` function to the value 1 and the variable `b`. Note the important distinction between the way `a` and `b` are used:
"""
# ╔═╡ 0a5469c9-ccef-4b62-9f0f-d3552fb3b986
md"""
* The value of the *variable* `a` at expression construction time is used as an immediate value in the expression. Thus, the value of `a` when the expression is evaluated no longer matters: the value in the expression is already `1`, independent of whatever the value of `a` might be.
* On the other hand, the *symbol* `:b` is used in the expression construction, so the value of the variable `b` at that time is irrelevant – `:b` is just a symbol and the variable `b` need not even be defined. At expression evaluation time, however, the value of the symbol `:b` is resolved by looking up the value of the variable `b`.
"""
# ╔═╡ 91b8a80e-5f65-4387-acf2-5fb87692789d
md"""
### Functions on `Expr`essions
"""
# ╔═╡ 9ee35586-1af3-43ab-b1ba-5b3bb67ebb34
md"""
As hinted above, one extremely useful feature of Julia is the capability to generate and manipulate Julia code within Julia itself. We have already seen one example of a function returning [`Expr`](@ref) objects: the [`parse`](@ref) function, which takes a string of Julia code and returns the corresponding `Expr`. A function can also take one or more `Expr` objects as arguments, and return another `Expr`. Here is a simple, motivating example:
"""
# ╔═╡ 5e0c39cc-b506-45de-897e-afd46895836d
function math_expr(op, op1, op2)
expr = Expr(:call, op, op1, op2)
return expr
end
# ╔═╡ 8b221b0f-36b2-4ac5-9957-68b2168ba517
ex = math_expr(:+, 1, Expr(:call, :*, 4, 5))
# ╔═╡ f8cefa6a-eece-4aca-930f-455eb7fe1914
eval(ex)
# ╔═╡ 5e3b7f2c-e329-4273-9a03-6d8a73608111
md"""
As another example, here is a function that doubles any numeric argument, but leaves expressions alone:
"""
# ╔═╡ 3e01b90a-d9eb-4b71-809b-0dc9be003933
function make_expr2(op, opr1, opr2)
opr1f, opr2f = map(x -> isa(x, Number) ? 2*x : x, (opr1, opr2))
retexpr = Expr(:call, op, opr1f, opr2f)
return retexpr
end
# ╔═╡ 73f1405c-a7c0-498a-a038-dadec51ecd27
make_expr2(:+, 1, 2)
# ╔═╡ 4ded730f-d9bf-4c02-8631-c7c51afe9a87
ex = make_expr2(:+, 1, Expr(:call, :*, 5, 8))
# ╔═╡ c3e1d72f-f4a9-49a8-9463-f339cb0b7b2c
eval(ex)
# ╔═╡ 64f2099f-1105-44a7-81a6-a994a9a04d42
md"""
## [Macros](@id man-macros)
"""
# ╔═╡ f048bbf0-6d7a-4a59-9399-d54097ccf829
md"""
Macros provide a method to include generated code in the final body of a program. A macro maps a tuple of arguments to a returned *expression*, and the resulting expression is compiled directly rather than requiring a runtime [`eval`](@ref) call. Macro arguments may include expressions, literal values, and symbols.
"""
# ╔═╡ 3ce27869-3376-43d9-b7f2-8f7fec825e2e
md"""
### Basics
"""
# ╔═╡ ecf455bc-76fe-4cbe-b953-67371e614292
md"""
Here is an extraordinarily simple macro:
"""
# ╔═╡ 75c33720-2ad3-4b7f-a793-a9c2d92a984a
macro sayhello()
return :( println("Hello, world!") )
end
# ╔═╡ e22c2662-6933-4715-98cd-6d7c4db46a0c
md"""
Macros have a dedicated character in Julia's syntax: the `@` (at-sign), followed by the unique name declared in a `macro NAME ... end` block. In this example, the compiler will replace all instances of `@sayhello` with:
"""
# ╔═╡ ed26c3c1-a93f-4f69-b654-2b419503d422
md"""
```julia
:( println(\"Hello, world!\") )
```
"""
# ╔═╡ 3513f124-bec4-48fc-bf89-f9dafe70f9f4
md"""
When `@sayhello` is entered in the REPL, the expression executes immediately, thus we only see the evaluation result:
"""
# ╔═╡ 71415783-382f-4f44-88b5-61d4d60faa30
@sayhello()
# ╔═╡ 0963a1f3-aac8-40e6-93d5-6fd6e8ba0730
md"""
Now, consider a slightly more complex macro:
"""
# ╔═╡ 3047d86a-1a72-414d-a07e-da6bd9201be9
macro sayhello(name)
return :( println("Hello, ", $name) )
end
# ╔═╡ b46ab3b3-8046-4e89-af4a-50f0b312c9b7
md"""
This macro takes one argument: `name`. When `@sayhello` is encountered, the quoted expression is *expanded* to interpolate the value of the argument into the final expression:
"""
# ╔═╡ f77e4f70-c123-4c6e-b796-9c04bddf1109
@sayhello("human")
# ╔═╡ fee9f20b-9215-4d74-b8d1-dda548e424d6
md"""
We can view the quoted return expression using the function [`macroexpand`](@ref) (**important note:** this is an extremely useful tool for debugging macros):
"""
# ╔═╡ 14921103-4720-44b9-bd67-c390278cc0e4
ex = macroexpand(Main, :(@sayhello("human")) )
# ╔═╡ 0c866a9a-e612-4730-b528-9d506ea19fe7
typeof(ex)
# ╔═╡ 63471543-034d-4f19-811b-32d64d4fd115
md"""
We can see that the `\"human\"` literal has been interpolated into the expression.
"""
# ╔═╡ fb4452b8-c16a-4bbf-a6dc-c142c11998cf
md"""
There also exists a macro [`@macroexpand`](@ref) that is perhaps a bit more convenient than the `macroexpand` function:
"""
# ╔═╡ bc63e678-3b03-4330-bb5c-b00fd610bd46
@macroexpand @sayhello "human"
# ╔═╡ 294ac76a-e11c-43f8-b9b9-1fc5b9a36871
md"""
### Hold up: why macros?
"""
# ╔═╡ cd2366f7-6528-4ae6-83cb-dc82b89287b1
md"""
We have already seen a function `f(::Expr...) -> Expr` in a previous section. In fact, [`macroexpand`](@ref) is also such a function. So, why do macros exist?
"""
# ╔═╡ f978c9c7-ec5b-4162-92b4-95b16efed6ad
md"""
Macros are necessary because they execute when code is parsed, therefore, macros allow the programmer to generate and include fragments of customized code *before* the full program is run. To illustrate the difference, consider the following example:
"""
# ╔═╡ c7a9233d-374a-4981-8e43-166d8a3ff042
macro twostep(arg)
println("I execute at parse time. The argument is: ", arg)
return :(println("I execute at runtime. The argument is: ", $arg))
end
# ╔═╡ f72565d4-8f25-4cb4-9f7f-19dff4cf78da
ex = macroexpand(Main, :(@twostep :(1, 2, 3)) );
# ╔═╡ 30004803-2216-4b8d-9267-1d3dbd7ab072
md"""
The first call to [`println`](@ref) is executed when [`macroexpand`](@ref) is called. The resulting expression contains *only* the second `println`:
"""
# ╔═╡ 2d4bfe38-5121-4128-a0e0-165f16cacd70
typeof(ex)
# ╔═╡ 6e65d136-ad27-4db6-abf3-f8e6bfc7b83c
ex
# ╔═╡ d650ea8f-589b-4cf3-9c55-d4f24a6b9a3d
eval(ex)
# ╔═╡ 95e0e154-7479-4216-952f-55c33200ab17
md"""
### Macro invocation
"""
# ╔═╡ 364f58d3-706b-4a99-83a7-d72d2c2acc1b
md"""
Macros are invoked with the following general syntax:
"""
# ╔═╡ b90e2c60-561a-4afd-a41f-c0b0b87ca60c
md"""
```julia
@name expr1 expr2 ...
@name(expr1, expr2, ...)
```
"""
# ╔═╡ 589dbb6d-bfa0-449f-8bfe-da83ebaa6f6e
md"""
Note the distinguishing `@` before the macro name and the lack of commas between the argument expressions in the first form, and the lack of whitespace after `@name` in the second form. The two styles should not be mixed. For example, the following syntax is different from the examples above; it passes the tuple `(expr1, expr2, ...)` as one argument to the macro:
"""
# ╔═╡ cc8f4ed9-7dac-45d2-8d59-4171939c9915
md"""
```julia
@name (expr1, expr2, ...)
```
"""
# ╔═╡ 4be03b12-208b-4034-9056-3100b69486d1
md"""
An alternative way to invoke a macro over an array literal (or comprehension) is to juxtapose both without using parentheses. In this case, the array will be the only expression fed to the macro. The following syntax is equivalent (and different from `@name [a b] * v`):
"""
# ╔═╡ 1c556d0a-5525-4e2c-80f2-0207af774553
md"""
```julia
@name[a b] * v
@name([a b]) * v
```
"""
# ╔═╡ 309a286b-78d9-4ff0-859c-a6a6f0abf34b
md"""
It is important to emphasize that macros receive their arguments as expressions, literals, or symbols. One way to explore macro arguments is to call the [`show`](@ref) function within the macro body:
"""
# ╔═╡ 336da33b-aa63-4bae-a82a-f847fa40b9b0
macro showarg(x)
show(x)
# ... remainder of macro, returning an expression
end
# ╔═╡ 560f7df8-821d-43cc-8077-ab98b0779296
@showarg(a)
# ╔═╡ 8953fe37-0e8b-4d00-816e-a1050acc8635
@showarg(1+1)
# ╔═╡ a64d78c8-8728-4fbe-9bb6-311bde057a43
@showarg(println("Yo!"))
# ╔═╡ 58a065aa-b40e-46e9-be19-733bdad3837b
md"""
In addition to the given argument list, every macro is passed extra arguments named `__source__` and `__module__`.
"""
# ╔═╡ 9aba8e4a-3f76-45ed-add5-e0f5f6fc5485
md"""
The argument `__source__` provides information (in the form of a `LineNumberNode` object) about the parser location of the `@` sign from the macro invocation. This allows macros to include better error diagnostic information, and is commonly used by logging, string-parser macros, and docs, for example, as well as to implement the [`@__LINE__`](@ref), [`@__FILE__`](@ref), and [`@__DIR__`](@ref) macros.
"""
# ╔═╡ 884121d0-9980-40bc-bcb3-c941ca75f206
md"""
The location information can be accessed by referencing `__source__.line` and `__source__.file`:
"""
# ╔═╡ a66a975e-1af7-4403-b9ce-c88ba9695a86
macro __LOCATION__(); return QuoteNode(__source__); end
# ╔═╡ df7198a4-b334-44c5-9396-ca0dc79f2395
dump(
@__LOCATION__(
))
# ╔═╡ 2211b1cc-d9dd-4bd5-bf55-0dcdab169317
md"""
The argument `__module__` provides information (in the form of a `Module` object) about the expansion context of the macro invocation. This allows macros to look up contextual information, such as existing bindings, or to insert the value as an extra argument to a runtime function call doing self-reflection in the current module.
"""
# ╔═╡ 1a620020-ab6d-4454-bc60-fa727d2be594
md"""
### Building an advanced macro
"""
# ╔═╡ 9057076e-ab4f-46d2-9f50-9b56f86f9445
md"""
Here is a simplified definition of Julia's [`@assert`](@ref) macro:
"""
# ╔═╡ 214b0788-a678-482a-b9e1-ba388ee41207
macro assert(ex)
return :( $ex ? nothing : throw(AssertionError($(string(ex)))) )
end
# ╔═╡ 91bc0ab4-44ca-4bb6-8543-4dcd0eb042ed
md"""
This macro can be used like this:
"""
# ╔═╡ 3fb1e2a0-393b-4051-8fc1-caf74e997ca2
@assert 1 == 1.0
# ╔═╡ 38f53163-88f0-4532-b6c0-cda08ed9f551
@assert 1 == 0
# ╔═╡ cb20682c-1d0c-4d88-b5e8-6792ec54bbd9
md"""
In place of the written syntax, the macro call is expanded at parse time to its returned result. This is equivalent to writing:
"""
# ╔═╡ bb43e482-196c-4864-a0ff-40e767914429
md"""
```julia
1 == 1.0 ? nothing : throw(AssertionError(\"1 == 1.0\"))
1 == 0 ? nothing : throw(AssertionError(\"1 == 0\"))
```
"""
# ╔═╡ 1c4f7db3-c88a-4bde-a418-5bfdc7809409
md"""
That is, in the first call, the expression `:(1 == 1.0)` is spliced into the test condition slot, while the value of `string(:(1 == 1.0))` is spliced into the assertion message slot. The entire expression, thus constructed, is placed into the syntax tree where the `@assert` macro call occurs. Then at execution time, if the test expression evaluates to true, then [`nothing`](@ref) is returned, whereas if the test is false, an error is raised indicating the asserted expression that was false. Notice that it would not be possible to write this as a function, since only the *value* of the condition is available and it would be impossible to display the expression that computed it in the error message.
"""
# ╔═╡ e8777260-736f-4006-a3ed-23e2b61b52de
md"""
The actual definition of `@assert` in Julia Base is more complicated. It allows the user to optionally specify their own error message, instead of just printing the failed expression. Just like in functions with a variable number of arguments ([Varargs Functions](@ref)), this is specified with an ellipses following the last argument:
"""
# ╔═╡ c0e000b2-f297-47c3-9ad5-5570494a6204
macro assert(ex, msgs...)
msg_body = isempty(msgs) ? ex : msgs[1]
msg = string(msg_body)
return :($ex ? nothing : throw(AssertionError($msg)))
end
# ╔═╡ ff48656f-4367-49ca-bea6-65d5113fd0cc
md"""
Now `@assert` has two modes of operation, depending upon the number of arguments it receives! If there's only one argument, the tuple of expressions captured by `msgs` will be empty and it will behave the same as the simpler definition above. But now if the user specifies a second argument, it is printed in the message body instead of the failing expression. You can inspect the result of a macro expansion with the aptly named [`@macroexpand`](@ref) macro:
"""
# ╔═╡ 5f71e871-d6d7-4de5-94f4-226d83741df7
@macroexpand @assert a == b
# ╔═╡ b6db6abb-0c47-4c36-b533-fb23b51cad5c
@macroexpand @assert a==b "a should equal b!"
# ╔═╡ 5b78bbfc-41f2-4073-8903-e538da17d0e0
md"""
There is yet another case that the actual `@assert` macro handles: what if, in addition to printing \"a should equal b,\" we wanted to print their values? One might naively try to use string interpolation in the custom message, e.g., `@assert a==b \"a ($a) should equal b ($b)!\"`, but this won't work as expected with the above macro. Can you see why? Recall from [string interpolation](@ref string-interpolation) that an interpolated string is rewritten to a call to [`string`](@ref). Compare:
"""
# ╔═╡ 51429238-d677-471f-b73b-fde1828ed060
typeof(:("a should equal b"))
# ╔═╡ f0191618-680f-47c0-b6fa-6e4a6edf3f2f
typeof(:("a ($a) should equal b ($b)!"))
# ╔═╡ 2d5f5dbc-10a6-4f37-96bb-af98cfeadc42
dump(:("a ($a) should equal b ($b)!"))
# ╔═╡ c46af693-c9fc-405f-8b05-70b395f5b8b2
md"""
So now instead of getting a plain string in `msg_body`, the macro is receiving a full expression that will need to be evaluated in order to display as expected. This can be spliced directly into the returned expression as an argument to the [`string`](@ref) call; see [`error.jl`](https://github.com/JuliaLang/julia/blob/master/base/error.jl) for the complete implementation.
"""
# ╔═╡ 72c5f262-09ed-4865-acc0-7819b3e439bd
md"""
The `@assert` macro makes great use of splicing into quoted expressions to simplify the manipulation of expressions inside the macro body.
"""
# ╔═╡ b02f2238-11bd-4e82-a8d8-e3ba2218dffb
md"""
### Hygiene
"""
# ╔═╡ f2568ed4-a219-4d42-be7d-cae074280740
md"""
An issue that arises in more complex macros is that of [hygiene](https://en.wikipedia.org/wiki/Hygienic_macro). In short, macros must ensure that the variables they introduce in their returned expressions do not accidentally clash with existing variables in the surrounding code they expand into. Conversely, the expressions that are passed into a macro as arguments are often *expected* to evaluate in the context of the surrounding code, interacting with and modifying the existing variables. Another concern arises from the fact that a macro may be called in a different module from where it was defined. In this case we need to ensure that all global variables are resolved to the correct module. Julia already has a major advantage over languages with textual macro expansion (like C) in that it only needs to consider the returned expression. All the other variables (such as `msg` in `@assert` above) follow the [normal scoping block behavior](@ref scope-of-variables).
"""
# ╔═╡ a4697205-d518-4704-a6f0-26d3152da54a
md"""
To demonstrate these issues, let us consider writing a `@time` macro that takes an expression as its argument, records the time, evaluates the expression, records the time again, prints the difference between the before and after times, and then has the value of the expression as its final value. The macro might look like this:
"""
# ╔═╡ 0ebea417-c177-4636-9d09-5315487a908d
md"""
```julia
macro time(ex)
return quote
local t0 = time_ns()
local val = $ex
local t1 = time_ns()
println(\"elapsed time: \", (t1-t0)/1e9, \" seconds\")
val
end
end
```
"""
# ╔═╡ 495471b9-722d-420b-b2f6-2bebb570637e
md"""
Here, we want `t0`, `t1`, and `val` to be private temporary variables, and we want `time_ns` to refer to the [`time_ns`](@ref) function in Julia Base, not to any `time_ns` variable the user might have (the same applies to `println`). Imagine the problems that could occur if the user expression `ex` also contained assignments to a variable called `t0`, or defined its own `time_ns` variable. We might get errors, or mysteriously incorrect behavior.
"""
# ╔═╡ 5410304a-635a-4a46-97b3-3f186e548212
md"""
Julia's macro expander solves these problems in the following way. First, variables within a macro result are classified as either local or global. A variable is considered local if it is assigned to (and not declared global), declared local, or used as a function argument name. Otherwise, it is considered global. Local variables are then renamed to be unique (using the [`gensym`](@ref) function, which generates new symbols), and global variables are resolved within the macro definition environment. Therefore both of the above concerns are handled; the macro's locals will not conflict with any user variables, and `time_ns` and `println` will refer to the Julia Base definitions.
"""
# ╔═╡ 59e0100b-34cb-4169-b56e-72b405b47254
md"""
One problem remains however. Consider the following use of this macro:
"""
# ╔═╡ ef62fc92-8fc8-4979-a224-d0681fb4c4cb
md"""
```julia
module MyModule
import Base.@time
time_ns() = ... # compute something
@time time_ns()
end
```
"""
# ╔═╡ 5242bb32-5da5-4bc7-a2f7-a742bcd8dba1
md"""
Here the user expression `ex` is a call to `time_ns`, but not the same `time_ns` function that the macro uses. It clearly refers to `MyModule.time_ns`. Therefore we must arrange for the code in `ex` to be resolved in the macro call environment. This is done by \"escaping\" the expression with [`esc`](@ref):
"""
# ╔═╡ fe083f76-0186-483c-88fb-8cf452c9c572
md"""
```julia
macro time(ex)
...
local val = $(esc(ex))
...
end
```
"""
# ╔═╡ 315e2bcf-b794-4f34-b158-9b03d3560907
md"""
An expression wrapped in this manner is left alone by the macro expander and simply pasted into the output verbatim. Therefore it will be resolved in the macro call environment.
"""
# ╔═╡ 30f8abfb-b8f5-4e3a-95e9-493254f393e1
md"""
This escaping mechanism can be used to \"violate\" hygiene when necessary, in order to introduce or manipulate user variables. For example, the following macro sets `x` to zero in the call environment:
"""
# ╔═╡ a35e724c-990f-4285-803d-8b0f9416c210
macro zerox()
return esc(:(x = 0))
end
# ╔═╡ bc43c33b-c8c5-4c87-830d-f399e3045829
function foo()
x = 1
@zerox
return x # is zero
end
# ╔═╡ 9e5a5f02-740e-489e-87d0-f36ac029e73a
foo()
# ╔═╡ 227d08c8-0058-4da3-a590-5092da62c776
md"""
This kind of manipulation of variables should be used judiciously, but is occasionally quite handy.
"""
# ╔═╡ c4c18d92-a287-4824-99ba-df08bf1f726e
md"""
Getting the hygiene rules correct can be a formidable challenge. Before using a macro, you might want to consider whether a function closure would be sufficient. Another useful strategy is to defer as much work as possible to runtime. For example, many macros simply wrap their arguments in a `QuoteNode` or other similar [`Expr`](@ref). Some examples of this include `@task body` which simply returns `schedule(Task(() -> $body))`, and `@eval expr`, which simply returns `eval(QuoteNode(expr))`.
"""
# ╔═╡ 22d6de99-7df5-4c8f-b7e0-52d469f94c83
md"""
To demonstrate, we might rewrite the `@time` example above as:
"""
# ╔═╡ 124b5200-4a27-45b7-bd16-10ed18deab49
md"""
```julia
macro time(expr)
return :(timeit(() -> $(esc(expr))))
end
function timeit(f)
t0 = time_ns()
val = f()
t1 = time_ns()
println(\"elapsed time: \", (t1-t0)/1e9, \" seconds\")
return val
end
```
"""
# ╔═╡ a016c4cb-3c51-4b13-a303-a399c7b9a72e
md"""
However, we don't do this for a good reason: wrapping the `expr` in a new scope block (the anonymous function) also slightly changes the meaning of the expression (the scope of any variables in it), while we want `@time` to be usable with minimum impact on the wrapped code.
"""
# ╔═╡ 1c72d267-1d88-4133-896a-9ff51a3b4a04
md"""
### Macros and dispatch
"""
# ╔═╡ cc237a28-d512-4cca-b529-45771cc5b4de
md"""
Macros, just like Julia functions, are generic. This means they can also have multiple method definitions, thanks to multiple dispatch:
"""
# ╔═╡ 71771d99-ad11-4ea5-93f6-4f0254548d61
macro m end
# ╔═╡ de94ec4d-04c9-4568-9ce4-ab938cdb1ac1
macro m(args...)
println("$(length(args)) arguments")
end
# ╔═╡ ebf78bef-6d08-4d95-8cd8-35352698d2b4
macro m(x,y)
println("Two arguments")
end
# ╔═╡ 3ebe7ef8-b9e8-48cf-99c9-d4437299d28f
@m "asd"
# ╔═╡ ddd41fa7-5f8a-4ed6-bef9-39c2ed9943d0
@m 1 2
# ╔═╡ 86facd67-9b0a-417a-9afc-d2aeef1b1f8d
md"""
However one should keep in mind, 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:
"""
# ╔═╡ 0d9fb05c-a900-4d87-adfd-1ff2c0138095
macro m(::Int)
println("An Integer")
end
# ╔═╡ b66f0390-cf77-4c99-bc01-18369767114e
@m 2
# ╔═╡ 37736dbd-b54a-4f4a-adee-289177b16a40
x = 2
# ╔═╡ bf82c5af-44c1-4e4e-8f71-112ed0d33ead
@m x
# ╔═╡ fad933a9-a92e-4dd9-ba0b-93454a2e6c35
md"""
## Code Generation
"""
# ╔═╡ 4dcd6922-0e9e-47c3-b967-c54ec43e294b
md"""
When a significant amount of repetitive boilerplate code is required, it is common to generate it programmatically to avoid redundancy. In most languages, this requires an extra build step, and a separate program to generate the repetitive code. In Julia, expression interpolation and [`eval`](@ref) allow such code generation to take place in the normal course of program execution. For example, consider the following custom type
"""
# ╔═╡ fb92155a-98b4-4245-9e0a-d36a1a67b6b5
struct MyNumber
x::Float64
end
# ╔═╡ d18777b3-b477-4194-b9dc-c2e769444776
md"""
for which we want to add a number of methods to. We can do this programmatically in the following loop:
"""
# ╔═╡ a905eca7-1b19-4a2b-a944-07e5820b67c7
for op = (:sin, :cos, :tan, :log, :exp)
eval(quote
Base.$op(a::MyNumber) = MyNumber($op(a.x))
end)
end
# ╔═╡ 1fbb990a-40b7-4b9f-bf2f-ef75e03f9dd0
md"""
and we can now use those functions with our custom type:
"""