You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- 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
296
310
297
311
#### Macros: The Building Blocks of Elixir
298
312
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.
300
315
301
-
[Second macro](unless/unless.exs) - `unless.exs`
316
+
[Second macro](unless/unless.exs) - `unless.exs` also [.iex.exs](unless/.iex.exs)
302
317
303
318
<details>
304
319
<summary>unless.exs</summary>
305
320
306
321
```elixir
307
322
defmoduleControlFlowdo
308
-
defmacrounless(expression, do: block) do
323
+
defmacrounless(expression, do: block) do
309
324
quotedo
310
-
if!unquote(expression), do:unquote(block)
325
+
if!unquote(expression), do:unquote(block)
326
+
end
311
327
end
312
-
end
313
328
end
314
329
```
315
330
331
+
Output:
332
+
333
+
```elixir
334
+
iex>ControlFlow.unless2==5, do:"block entered"
335
+
"block entered"
336
+
337
+
iex>ControlFlow.unless5==5, do:"block entered"
338
+
nil
339
+
```
340
+
316
341
</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