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).