Source guide/aspects.simd
1# Aspects 2 3An **:gloss[aspect](./A1-glossary.simd#aspect)** is a capability you 4want available throughout a program without signing for it at 5every call site. Logging, tracing, metrics, localisation, the 6current random-number generator, the current time source for 7testing — all of them are things that plenty of words need and 8that nobody wants to list as an effect on the signature of 9every word in between. 10 11Aspects fill that gap. They look like effects from a 12declaration-site perspective, but they don't need to appear in 13call-site signatures. Instead, every aspect operation has a 14**default** implementation (usually a no-op), and you can 15**override** the implementation for a dynamically scoped region 16with `:with`. 17 18If you've used AspectJ or Common Lisp's `:around` methods, this 19is the same idea in a typed-stack form. If you've used Koka, 20aspects are Silo's un-annotated version of its algebraic effects 21— less tracking at call sites, more flexibility for 22cross-cutting concerns. 23 24## Declaring an aspect 25 26`:aspect` declares a named capability with one or more 27operations. Each operation gets a default body, separated from 28its signature by a space and ending with `;`: 29 30```silo 31:aspect Log 32 .log ( Str LogLevel -> ) drop drop ; 33:end 34``` 35 36`Log` has one operation, `.log`, which pops a `LogLevel` (top 37of stack) and a `Str` (underneath). The default `drop drop` 38discards both — the no-op case. 39Every aspect operation **must** have a default, because code 40calls the operation without first checking that a handler is 41installed. 42 43## Implementing a handler 44 45An aspect handler is a value whose type implements the aspect 46for its own case: 47 48```silo 49:record StdoutLogger :end 50 51:impl Log StdoutLogger 52 .log ( Str LogLevel -> ) +Console 53 "{}: {}" format print ; 54:end 55``` 56 57`StdoutLogger` is a zero-field record used as a tag — in this 58case the handler carries no state. The `:impl` block implements 59`Log` for `StdoutLogger`, replacing the default `.log` body with 60one that prints. Notice that this handler's `.log` declares 61`+Console`: a handler can do whatever work it needs, with 62whatever effects it needs, independent of what the call site 63sees. 64 65Handlers with state are records with fields: 66 67```silo 68:record RingLogger 69 .buffer (Vec (Pair Str LogLevel) 128) 70 .pos U8 71:end 72``` 73 74and an `:impl` that reads/updates the state appropriately. 75 76## Importing an aspect 77 78The `:use` block imports both the aspect itself and the 79operations it exports: 80 81```silo 82:use 83 :aspect logging Log 84:end 85``` 86 87The first name (`logging`) is the package containing the aspect; 88the second (`Log`) is the aspect's name. After this import, the 89operations — here `.log` — are available as ordinary methods. 90 91## Calling an aspect operation 92 93Call sites don't need any annotation: 94 95```silo 96:fn do-work ( -> ) 97 "working" LInfo .log # no +Log effect declared anywhere 98:end 99``` 100 101The `.log` call resolves against whichever handler is currently 102installed. If no `:with` block is active, it dispatches to the 103default (the aspect's built-in no-op body). 104 105This is the crucial property of aspects. `do-work` has a pure 106signature. You can call it from anywhere without worrying about 107whether the caller has a `+Log` capability, because there's no 108such effect to propagate. The logging happens if a handler is 109installed; it silently doesn't otherwise. 110 111## Installing a handler — `:with` 112 113`:with` installs a handler for a dynamically scoped region: 114 115```silo 116:main +Console 117 StdoutLogger :with Log 118 do-work # .log calls go to StdoutLogger 119 :end 120 do-work # .log calls are no-ops again 121:end 122``` 123 124Read `StdoutLogger :with Log … :end` as "for the body, use 125`StdoutLogger` as the handler for the `Log` aspect." The 126handler value sits on the stack immediately before `:with`; the 127body runs with the handler installed; after `:end`, the previous 128handler (or the default) is restored. 129 130Because `:with` is dynamic, nested `:with` blocks nest: 131 132```silo 133:main +Console 134 StdoutLogger :with Log 135 do-work # StdoutLogger 136 RingLogger :with Log 137 do-work # RingLogger, inside the inner :with 138 :end 139 do-work # back to StdoutLogger 140 :end 141:end 142``` 143 144That's what makes aspects practical for tests and experiments: 145wrap a region in a mock handler and the code inside runs 146unchanged, dispatching to the mock. 147 148## Aspects versus effects 149 150The two feel related because both concern cross-cutting 151behaviour, and both use `:with` for handler installation. The 152differences: 153 154| Dimension | Effect | Aspect | 155|-----------------------|----------------------------------|---------------------------------| 156| Declaration keyword | `:effect` | `:aspect` | 157| Handler required? | Yes (host or `:with`) | No — every op has a default | 158| Shows in signatures? | Yes (`+Effect`) | No | 159| Call-site ceremony | Caller must declare/discharge | Call freely, dispatches to default/handler | 160| Runtime representation | Same vtable-backed handler install | Same vtable-backed handler install | 161 162Use an effect when the operation *has* to succeed — if there's 163no handler, something has gone wrong and the program shouldn't 164compile. Use an aspect when the operation is *cross-cutting* — 165nice to have everywhere, with a sensible no-op default when 166nobody's listening. 167 168## When to reach for an aspect 169 170A word needs aspects if, hypothetically, you could replace every 171one of its operations with a no-op and the program would still 172produce the right answer — just without the side observation. 173Logging passes that test. So does metrics collection, tracing, 174and "the current locale" (the default is the system locale; a 175`:with` overrides). 176 177A word that *genuinely* needs the capability — reading a file, 178making a network call — should be an effect, not an aspect. 179Aspects are for behaviour that's worth providing but isn't 180essential to correctness. 181 182## Key points 183 184- An aspect is a capability with a default. Operations are 185 callable anywhere, no `+Aspect` annotation needed. 186- `:aspect` declares; `:impl (Aspect Type)` provides a handler; 187 `:with` installs a handler for a dynamically scoped region. 188- Handlers are values. Stateless handlers are zero-field 189 records used as tags; stateful handlers are records with 190 fields. 191- Nested `:with` blocks nest dynamically — the innermost 192 handler wins for the scope of its body. 193- Aspects and effects have the same handler-install machinery 194 but different call-site stories: effects demand, aspects 195 invite. 196 197Next: [collections](./12-collections.simd) — the standard 198library's data structures, traversal traits, and iteration 199patterns.