Source guide/quotations.simd

1# Quotations and Higher-Order Functions
2
3A **:gloss[quotation](./A1-glossary.simd#quotation)** is a first-class
4block of code. You write one by wrapping some source in square
5brackets — `[ 2 * ]` — and the whole bracketed form becomes a value
6you can pass around, store in a record, return from a word, and
7eventually invoke.
8
9If you know Rust, this is a :gloss[closure](./A1-glossary.simd#closure). If
10you know ML or Haskell, it is a lambda. Silo calls it a quotation
11because that's the name the Forth and Joy families use for the same
12construct, and the language inherits enough of their idioms that the
13old name still fits.
14
15This chapter introduces three things, in order:
16
171. How to make a quotation and how to invoke it.
182. Its type, its :gloss[effect row](./A1-glossary.simd#effect-row), and
19   the thing that makes Silo quotations especially composable:
20   :gloss[row polymorphism](./A1-glossary.simd#row-variable).
213. How :gloss[higher-order](./A1-glossary.simd#higher-order-function)
22   library words like `.map`, `.filter`, `.fold`, and `.for-each`
23   use quotations to drive ordinary iteration.
24
25## Writing and invoking a quotation
26
27A quotation is a block in square brackets:
28
29```silo
30[ 2 * ]
31```
32
33Pushed on its own, this leaves a quotation value on the stack. To
34actually run the code inside, call `.call`:
35
36```silo
375 [ 2 * ] .call              # ⌊10⌉
38```
39
40Reading it left to right: push `5`, push the quotation `[ 2 * ]`, then
41`.call` pops the quotation and runs its body — which pops `5`, pushes
42`10`. The `.call` method handles both the invocation and the
43argument-passing in one step, because the quotation's body operates
44on the same stack as the caller.
45
46## Capturing bindings
47
48Quotations are closures: they capture `pop->` and `peek->` bindings
49from the surrounding word. Here's a factory that builds a custom
50adder:
51
52```silo
53:fn make-adder ( Int -> [Int -> Int] )
54  pop-> n
55  [ n + ]
56:end
57
5810 make-adder pop-> add10
595 add10 .call                 # ⌊15⌉
60```
61
62`make-adder` binds `n` from its argument, then returns a quotation
63that references `n`. When you later `.call` the quotation, the `n` is
64still there — just like a Rust `move` closure or an ML lambda. You
65don't need to annotate anything: the compiler works out what got
66captured.
67
68## The type of a quotation
69
70The signature `[Int -> Int]` in the example above is the **quotation
71type**. It has the same shape as a word's
72:gloss[stack effect](./A1-glossary.simd#stack-effect):
73`[inputs -> outputs +effects]`. So `[Int -> Int]` is a quotation that
74pops an `Int` and pushes an `Int`, with no declared effects.
75
76Quotations can carry effects just like words can:
77
78```silo
79[ "{}" format print ]          # type: [Int -> +Console]
80```
81
82When a higher-order word is given an effectful quotation, the caller
83inherits those effects — covered in
84[the effects chapter](./10-effects.simd).
85
86## Row polymorphism
87
88Here's the part that deserves a pause.
89
90The quotation `[ 2 * ]` reads as "pop an `Int`, push an `Int`". But
91what actually happens to the rest of the stack? It stays untouched —
92`[ 2 * ]` doesn't care what's below the `Int` on top.
93
94Silo captures this formally in the quotation's type. The full type of
95`[ 2 * ]` is:
96
97```
98[..s Int -> ..s Int]
99```
100
101The `..s` is a **:gloss[stack row variable](./A1-glossary.simd#row-variable)**.
102It stands for "whatever was below the top of the stack, unchanged."
103Every quotation is implicitly row-polymorphic over its untouched
104stack tail.
105
106Row polymorphism is why `[ 2 * ]` works equally well as an argument
107to `.map`, as a standalone invocation on a stack with one `Int` on
108it, or as part of a longer pipeline where many values sit beneath it.
109You don't have to declare the row variable. You don't even have to
110know it's there. But it's what makes Silo's composition feel natural
111rather than ceremonial — a quotation that operates on the top two
112values composes with any stack shape that has those two values on
113top.
114
115If you've written Haskell, the closest analogue is the
116stack-type-variable machinery that Factor's and Kitten's type
117systems use. If you haven't, the short version is: quotations
118compose like Unix pipes. What's below flows through.
119
120## Higher-order functions
121
122Silo's library takes quotations as arguments everywhere it makes
123sense. The most common ones are methods on `Foldable` and on
124`Iterator` — `.map`, `.filter`, `.fold`, `.for-each`, `.reduce`,
125`.flat-map`, `.take`, `.drop`, and so on. They're covered in
126[the collections chapter](./12-collections.simd); here's a taste:
127
128```silo
1291 2 3 4 5 vec/5
130  [ 2 * ] .map                # [2 4 6 8 10]
131  [ 5 > ] .filter             # [6 8 10]
132  0 [ + ] .fold               # 24
133```
134
135Every one of these takes a quotation plus (for `.fold`) an initial
136value, and produces a transformed collection or a scalar. `.for-each`
137is similar but throws away the result — useful when the quotation is
138there for its side effects:
139
140```silo
1411 2 3 vec/3 [ "{}" format print ] .for-each
142```
143
144`.map`/`.filter`/`.fold` are eager. The lazy, element-at-a-time
145`Iterator` protocol is a sibling trait — reach for it when you want
146short-circuiting or infinite sequences. The mechanics are in
147[the collections chapter](./12-collections.simd) again.
148
149## Collecting a quotation into a Vec
150
151A quotation that pushes multiple values can be materialised into a
152Vec with `.collect`. This is how you build most literal `Vec` values
153in practice:
154
155```silo
156[ 1 2 3 4 5 ] .collect         # (Vec Int | 5)
157[ "a" "b" ] .collect           # (Vec Str | 2)
158```
159
160`.collect` relies on a feature of the stack-effect system called
161**:gloss[output variadics](./A1-glossary.simd#output-variadic)**. The quotation's body pushes values; the
162compiler counts them statically; `.collect` then gathers them into
163a Vec of exactly that length. The length ends up in the Vec's type,
164so a typo that produces too many or too few elements is caught at
165compile time.
166
167Output variadics have their own chapter
168([Variadics and Reflection](./19-variadic-and-reflection.simd)). For
169now, think of `[ … ] .collect` as the idiomatic way to produce a
170`Vec` literal.
171
172## Key points
173
174- `[ … ]` is a first-class closure. It captures surrounding `pop->`
175  and `peek->` bindings; the compiler works out the capture set
176  automatically.
177- `.call` invokes a quotation against the current stack.
178- Quotation types have the same shape as word signatures:
179  `[inputs -> outputs +effects]`.
180- Every quotation is **row-polymorphic** over its untouched stack
181  tail. That's why `[ 2 * ]` composes with `.map`, with `.fold`, and
182  with direct use, without any extra annotations.
183- Higher-order words like `.map`, `.filter`, `.fold`, and
184  `.for-each` are ordinary library functions that accept
185  quotations as arguments.
186- `[ … ] .collect` is the idiomatic way to build a `Vec` literal;
187  it uses output variadics so the length is compile-time known.
188
189The next chapter introduces
190[records, unions, enums, and the other named data types](./05-data.simd).