Skip to content

Commit 44936da

Browse files
committed
Finished Chapter 1
1 parent fd24b5d commit 44936da

File tree

1 file changed

+66
-19
lines changed

1 file changed

+66
-19
lines changed

README.md

Lines changed: 66 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ Moreover, don't expect direct quotes, changing the sentences vocbulary helps me
2222
- [High-Level Syntax vs. Low-level AST](#high-level-syntax-vs-low-level-ast)
2323
- [AST Literals](#ast-literals)
2424
- [Macros: The Building Blocks of Elixir](#macros-the-building-blocks-of-elixir)
25+
- [Macro Expansion](#macro-expansion)
26+
- [Code Injection and the Caller's Context](#code-injection-and-the-callers-context)
2527

2628
### Chapter 1 - The Language of Macros
2729

@@ -107,9 +109,21 @@ defmodule Math do
107109
end
108110
```
109111

112+
Output:
113+
114+
```elixir
115+
iex> Math.say 5 + 2
116+
5 plus 2 is 7
117+
7
118+
119+
iex> iex> Math.say 18 * 4
120+
18 times 4 is 72
121+
72
122+
```
123+
110124
</details>
111125

112-
Note when you use this in iex you need to first `c "math.exs"` then `require Math` but i've included it in [.iex.exs](.iex.exs) to save time.
126+
Note when you use this in iex you need to first `c "math.exs"` then `require Math` but i've included it in [.iex.exs](math/.iex.exs) to save time.
113127
Automagically adding these when you open iex with `iex math.exs`.
114128

115129
In this example. We take what we know from the AST representations so far from the `quote` we used. We then create `defmacro`-s. We can still
@@ -119,7 +133,7 @@ being the operator at the start of the AST, `{:+, ...}`, and use a new keyword c
119133
```elixir
120134
iex(1)> h unquote
121135

122-
defmacro unquote(expr)
136+
defmacro unquote(expr)
123137

124138
Unquotes the given expression from inside a macro.
125139

@@ -130,7 +144,7 @@ inside some quote. The first attempt would be:
130144

131145
value = 13
132146
quote do
133-
sum(1, value, 3)
147+
sum(1, value, 3)
134148
end
135149

136150
Which would then return:
@@ -192,13 +206,13 @@ iex(1)> quote do
192206
...(1)> end
193207
...(1)> end
194208
{:defmodule, [context: Elixir, import: Kernel],
195-
[
196-
{:__aliases__, [alias: false], [:MyModule]},
197-
[
198-
do: {:def, [context: Elixir, import: Kernel],
199-
[{:hello, [context: Elixir], Elixir}, [do: "World"]]}
200-
]
201-
]}
209+
[
210+
{:__aliases__, [alias: false], [:MyModule]},
211+
[
212+
do: {:def, [context: Elixir, import: Kernel],
213+
[{:hello, [context: Elixir], Elixir}, [do: "World"]]}
214+
]
215+
]}
202216
```
203217

204218
</details>
@@ -216,11 +230,11 @@ iex(1)> quote do
216230
```elixir
217231
iex(1)> quote do: (5 * 2) - 1 + 7
218232
{:+, [context: Elixir, import: Kernel],
219-
[
220-
{:-, [context: Elixir, import: Kernel],
233+
[
234+
{:-, [context: Elixir, import: Kernel],
221235
[{:*, [context: Elixir, import: Kernel], [5, 2]}, 1]},
222-
7
223-
]}
236+
7
237+
]}
224238
```
225239

226240
- An AST tree strunction of functions and arguments has been made. If we were to format this output into a more readable tree:
@@ -296,21 +310,54 @@ is, while more complex tuples are returned as a quoted expression. It's useful t
296310

297311
#### Macros: The Building Blocks of Elixir
298312

299-
- Let's imagine that `unless` does not exist in the elixir language. `unless` is essentially the same as a negated if statement, e.g. unless(1 + 1 = 5) would return true.
313+
- Let's imagine that `unless` does not exist in the elixir language. `unless` is essentially the same as a negated if statement, e.g.
314+
unless(1 + 1 = 5) would return true.
300315

301-
[Second macro](unless/unless.exs) - `unless.exs`
316+
[Second macro](unless/unless.exs) - `unless.exs` also [.iex.exs](unless/.iex.exs)
302317

303318
<details>
304319
<summary>unless.exs</summary>
305320

306321
```elixir
307322
defmodule ControlFlow do
308-
defmacro unless(expression, do: block) do
323+
defmacro unless(expression, do: block) do
309324
quote do
310-
if !unquote(expression), do: unquote(block)
325+
if !unquote(expression), do: unquote(block)
326+
end
311327
end
312-
end
313328
end
314329
```
315330

331+
Output:
332+
333+
```elixir
334+
iex> ControlFlow.unless 2 == 5, do: "block entered"
335+
"block entered"
336+
337+
iex> ControlFlow.unless 5 == 5, do: "block entered"
338+
nil
339+
```
340+
316341
</details>
342+
343+
- Since the macros receive the AST representation of arguments, we can accept any valid elixir expression as the first argument to `unless`.
344+
In the second argument we pattern match of the `do` blockand bind uts AST value to a variable.
345+
346+
- We then go straigh into our `quote` block where we build upon the `if` macro and simply negate it with `!`.
347+
348+
- We also of course `unquote` the parameters passed to the macro.
349+
350+
#### Macro Expansion
351+
352+
- When the compiler encounters a macro, it recursivley expands it until the code no loinger contains any macro calls.
353+
354+
- Take the code `ControlFlow.unless 2 == 5`. The compiler seeing this will ask if `unless` is a macro. If it is, expand it and see what's in that macro. In this case it goes into `unless` and finds `if !`. Is `if` a macro? Yes it is, so expand again to find `case !` is that a macro? No, expansion is now complete.
355+
- `case` case macro is a member of a small set of special macros, located in `Kernel.SpecialForms`. These macros are funfamental building blocks in Elixir that cannot be overridden. They also represent the end of the road for macro expansion.
356+
357+
#### Code Injection and the Caller's Context
358+
359+
- Elixir has the concept of macro _hygiene_. Hygiene means that variables, imports, and aliases that you define in a macro do not leak into the caller's own definitions.We must take special consideration with macro hygiene when expanding code, because sometimes it is necessary evil to implicitly access the caller's scope in an unhygenic way.
360+
- This safeguard also happens to prevent accidental namespace clashes.
361+
- This hygiene seems like a fancy version of just being in or out of scope, perhaps that's the point.
362+
- You can override this hygiene by pre-pending `var!`. For example, `if var!(meaning_to_life) == 42 do ....`
363+
- When working with macros, it's important to be aware of what context a macro is executing in and to respect hygiene.

0 commit comments

Comments
 (0)