GuideThe Silo HandbookAspectssource

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.