Skip to content

Commit d6cba13

Browse files
committed
restructure
1 parent 44936da commit d6cba13

File tree

3 files changed

+350
-342
lines changed

3 files changed

+350
-342
lines changed

README.md

Lines changed: 14 additions & 342 deletions
Original file line numberDiff line numberDiff line change
@@ -13,351 +13,23 @@ Moreover, don't expect direct quotes, changing the sentences vocbulary helps me
1313

1414
### Contents
1515

16-
- [Chapter 1 - The Language of Macros](#chapter-1-the-language-of-macros)
17-
- [What are Macros?](#what-are-macros)
18-
- [The Abstract Syntax Tree](#the-abstract-syntax-tree)
19-
- [Trying It All Together](#trying-it-all-together)
20-
- [Macro Rules](#macro-rules)
21-
- [The Abstract Syntax Tree - Demystified](#the-abstract-syntax-tree-demystified)
22-
- [High-Level Syntax vs. Low-level AST](#high-level-syntax-vs-low-level-ast)
23-
- [AST Literals](#ast-literals)
24-
- [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)
27-
28-
### Chapter 1 - The Language of Macros
29-
30-
#### What are Macros?
31-
32-
- Macros are code that write code.
33-
- Elixir itself is made with macros, as a result you can extend the language itself
34-
to include things you think you might need.
35-
- Metaprogramming in elixir serves the purpose of extensibility by design.
36-
- With this power one can even define languages within elixir. The following is a valid Elixir program.
37-
38-
```elixir
39-
div do
40-
h1 class: "title" do
41-
text "Hello"
42-
end
43-
p do
44-
text "Metaprogramming Elixir"
45-
end
46-
end
47-
"<div><h1 class=\"title\">Hello</h1><p>Metaprogramming Elixir</p></div>"
48-
```
49-
50-
#### The Abstract Syntax Tree
51-
52-
- Most languages use AST but you never need to know about them. They are used typically during compilation
53-
or interpretation to transform source code into a tree structure before being turned into bytecode
54-
or machine code..
55-
- José Valim, the creator of Elixir, chose to expose this AST and the syntax to interact with it.
56-
- We can now operate at the same level as the compiler.
57-
- Metaprogramming in Elixir revolves around manipulating and accessing ASTs.
58-
- To access the AST representation we use the `quote` macro.
59-
60-
```elixir
61-
iex> quote do: 1 + 2
62-
{:+, [context: Elixir, import: Kernel], [1, 2]}
63-
```
64-
65-
```elixir
66-
iex> quote do: div(10, 2)
67-
{:div, [context: Elixir, import: Kernel], [10, 2]}
68-
```
69-
70-
- This is the internals of the Elixir language itself.
71-
- This gives you easy options for infering meaning and optimising performance all while being within Elixirs high level syntax.
72-
- The purpose of macros is to interact with this AST with the syntax of Elixir.
73-
- Macros turn you from language consumer to language creator. You have the same level of power as José when he wrote the standard library.
74-
75-
#### Trying It All Together
76-
77-
"Let's write a macro that can print the spoken form of an Elixir mathematical expression, such as 5 + 2, when calculating a result.
78-
In most languages, we would have to parse a string expression into something digestible by our program. With Elixir, we can access
79-
the representation of expressions directly with macros."
80-
81-
[First macro - `math.exs`](math/math.exs)
82-
83-
<details>
84-
<summary>math.exs</summary>
85-
86-
```elixir
87-
defmodule Math do
88-
@moduledoc false
89-
90-
defmacro say({:+, _, [lhs, rhs]}) do
91-
quote do
92-
lhs = unquote(lhs)
93-
rhs = unquote(rhs)
94-
result = lhs + rhs
95-
IO.puts("#{lhs} plus #{rhs} is #{result}")
96-
result
97-
end
98-
end
99-
100-
defmacro say({:*, _, [lhs, rhs]}) do
101-
quote do
102-
lhs = unquote(lhs)
103-
rhs = unquote(rhs)
104-
result = lhs * rhs
105-
IO.puts("#{lhs} times #{rhs} is #{result}")
106-
result
107-
end
108-
end
109-
end
110-
```
111-
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-
124-
</details>
125-
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.
127-
Automagically adding these when you open iex with `iex math.exs`.
128-
129-
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
130-
have many function clauses with macros. With that, we create two macros called `say` and we pattern match on the AST with the defining feature
131-
being the operator at the start of the AST, `{:+, ...}`, and use a new keyword called `unquote`. From the docs:
132-
133-
```elixir
134-
iex(1)> h unquote
135-
136-
defmacro unquote(expr)
137-
138-
Unquotes the given expression from inside a macro.
139-
140-
## Examples
141-
142-
Imagine the situation you have a variable value and you want to inject it
143-
inside some quote. The first attempt would be:
144-
145-
value = 13
146-
quote do
147-
sum(1, value, 3)
148-
end
149-
150-
Which would then return:
151-
152-
{:sum, [], [1, {:value, [], quoted}, 3]}
153-
154-
Which is not the expected result. For this, we use unquote:
155-
156-
iex> value = 13
157-
iex> quote do
158-
...> sum(1, unquote(value), 3)
159-
...> end
160-
{:sum, [], [1, 13, 3]}
161-
```
162-
163-
I assume from this then when you pass variables to a macro they need to be `unqoute`-d, incontrast to passing a value directly.
164-
Which I'm not following as Elixir is pass-by-value so wouldn't the value just be known?
165-
166-
Turns out that yes that's correct because we are dealing with ASTs not the data it represents; therefore the pass-by-value argument doesn't hold.
167-
Much like interpolation from Ecto and the difference between `"Hello world"` and `"Hello #{world}`.
168-
169-
- We know that macros receive the AST representation of the arguments.
170-
- "To complete the macro, we used `quote` to return an AST for the caller to replace out `Math.say` invocations." I'm assuming by this that every macro needs some form of `quote`.
171-
172-
#### Macro Rules
173-
174-
- **Rule 1: Don't write macros.** We have to remember that writing code to produce code require special care. It's easy to get caught in a web of your own code generation. Too many macros can make debugging more difficult.
175-
176-
- **Rule: 2 Use macros gratuitously.** Metaprogramming is sometimes framed as complex and fragile as well as offering productive advantages in a fraction of the required code. It's important to keep this duality in mind when writing macros.
177-
178-
#### The Abstract Syntax Tree - Demystified
179-
180-
- Every expression you write in Elixir breaks down to a three-element tuple in the AST.
181-
- This uniform format makes pattern matching arguments a lot easier.
182-
- Quoting a couple more complex expressions to see how entire Elixir prograns are structure in the AST.
183-
184-
<details>
185-
<summary>(5 * 2 - 1 + 7)</summary>
186-
187-
```elixir
188-
iex(1)> quote do: (5 * 2) - 1 + 7
189-
{:+, [context: Elixir, import: Kernel],
190-
[
191-
{:-, [context: Elixir, import: Kernel],
192-
[{:*, [context: Elixir, import: Kernel], [5, 2]}, 1]},
193-
7
194-
]}
195-
```
196-
197-
</details>
198-
19916
<details>
200-
<summary>MyModule</summary>
201-
202-
```elixir
203-
iex(1)> quote do
204-
...(1)> defmodule MyModule do
205-
...(1)> def hello, do: "World"
206-
...(1)> end
207-
...(1)> end
208-
{:defmodule, [context: Elixir, import: Kernel],
209-
[
210-
{:__aliases__, [alias: false], [:MyModule]},
211-
[
212-
do: {:def, [context: Elixir, import: Kernel],
213-
[{:hello, [context: Elixir], Elixir}, [do: "World"]]}
214-
]
215-
]}
216-
```
17+
<summary>Chapter 1 - The Language of Macros</summary>
18+
19+
- [What are Macros?](chapters/chapter1.md/#what-are-macros)
20+
- [The Abstract Syntax Tree](#the-abstract-syntax-tree)
21+
- [Trying It All Together](#trying-it-all-together)
22+
- [Macro Rules](#macro-rules)
23+
- [The Abstract Syntax Tree - Demystified](#the-abstract-syntax-tree-demystified)
24+
- [High-Level Syntax vs. Low-level AST](#high-level-syntax-vs-low-level-ast)
25+
- [AST Literals](#ast-literals)
26+
- [Macros: The Building Blocks of Elixir](#macros-the-building-blocks-of-elixir)
27+
- [Macro Expansion](#macro-expansion)
28+
- [Code Injection and the Caller's Context](#code-injection-and-the-callers-context)
21729

21830
</details>
21931

220-
- A stacking tuple was produced from each quoted exoression. The first example shows the familiar structures used by our `Math.say` macro, but multiple tuples are stacked into an embedded tree to represent the entire expression. The result of the second example shows how an entire Eliir module represented by a simple AST.
221-
222-
- **All elixir code is represented as a series of three-element tuples with the following format:**
223-
224-
- **The first element** is an atom denoting the funcation call, or another tuple, representing a nested node in the AST.
225-
- **The second element** represents metadata about the expression.
226-
- **The third element** is a list of arguments for the function call.
227-
228-
- Applying this to the AST of `(5 * 2) - 1 + 7` from before
229-
230-
```elixir
231-
iex(1)> quote do: (5 * 2) - 1 + 7
232-
{:+, [context: Elixir, import: Kernel],
233-
[
234-
{:-, [context: Elixir, import: Kernel],
235-
[{:*, [context: Elixir, import: Kernel], [5, 2]}, 1]},
236-
7
237-
]}
238-
```
239-
240-
- An AST tree strunction of functions and arguments has been made. If we were to format this output into a more readable tree:
241-
242-
```bash
243-
+
244-
├── -
245-
│   ├── *
246-
│   │   ├── 5
247-
│   │   └── 7
248-
│   └── 1
249-
└── 7
250-
```
251-
252-
- It seems easiest to start from the end of the AST and work up. The root AST node is the `+` operator, and its arguments are
253-
the number 7 combined with another nested node in the tree. We can see that the nested nodes contain out `(5 * 2)` expression,
254-
whose results are applied to the `- 1` branch.
255-
- `5 * 2` is syntactic sugar for `Kernel.*(5, 2)`. This means the `:*` atom is just a function call from the import of `Kernel` as
256-
you can see from the AST output from before.
257-
258-
#### High-Level Syntax vs. Low-level AST
259-
260-
Comparing the AST from elixir to the source of Lisp. If you look closely, you can see how elixir operates at a layer just above this format.
261-
262-
For example, Lisp source code:
263-
264-
```lisp
265-
(+ (* 2 3) 1)
266-
```
267-
268-
And Elixir from source to generated AST:
269-
270-
```elixir
271-
quote do: 2 * 3 + 1
272-
{:+, _, [{:*, _, [2, 3]}, 1]}
273-
```
274-
275-
If we compare the both you can see that the structure itself is nearly identical with only the syntax being different. The beauty here is that
276-
the transformation from high-level source to low-level AST requires only a `quote` invocation. On the contrary, with Lisp you have all the power
277-
of a programmable AST at the cost of a less natural and flexible syntax. José seperated the AST from the syntax, meaning we get the best of both worlds.
278-
279-
#### AST Literals
280-
281-
- Playing with elixir source, sometimes the results of quoted expressions can appear confusing and irregular. This is because of literals. Literals
282-
have the same representation within the AST and at high-lvel source. This includes atoms, ints, floats, lists, strings and any two-element tuples
283-
containing the former types. Such as the following:
284-
285-
```elixir
286-
iex> quote do: :atom
287-
:atom
288-
289-
iex> quote do: 123
290-
123
291-
292-
iex> quote do: [1, 2, 3]
293-
294-
iex> quote do: {:ok, [1, 2, 3]}
295-
{:ok, [1, 2, 3]}
296-
```
297-
298-
If you pass any of the example to a macro, the macro receives the literal arguments instead of an abstract representation.
299-
300-
```elixir
301-
quoute do: %{a: 1, b: 2}
302-
{:%{}, [], [a: 1, b: 2]}
303-
304-
iex> quoute do: Enum
305-
{:__aliases__, [alias: false], [:Enum]}
306-
```
307-
308-
In these two examples we see that there are two different ways in which elixir types are represented in the AST. Some values are passed through as
309-
is, while more complex tuples are returned as a quoted expression. It's useful to keep literals in mind when writing macros to avoid confusion.
310-
311-
#### Macros: The Building Blocks of Elixir
312-
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.
315-
316-
[Second macro](unless/unless.exs) - `unless.exs` also [.iex.exs](unless/.iex.exs)
317-
31832
<details>
319-
<summary>unless.exs</summary>
320-
321-
```elixir
322-
defmodule ControlFlow do
323-
defmacro unless(expression, do: block) do
324-
quote do
325-
if !unquote(expression), do: unquote(block)
326-
end
327-
end
328-
end
329-
```
330-
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-
33+
<summary>Chapter 2 - Extending Elixir with Metaprogramming</summary>
34+
stuff
34135
</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)