fusion-lang 0.0.1.alpha1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,674 @@
1
+ # Fusion โ€” Design decision ledger
2
+
3
+ This document records the design decisions of the Fusion language: every decision made, who made it, the alternatives considered, and the trade-offs.
4
+
5
+ Future work and open questions are tracked separately in our [Roadmap](./roadmap.md).
6
+
7
+ **Attribution legend:**
8
+
9
+ - ๐Ÿง‘ **Designer** โ€” decided by the language designer during the active design conversation (the human author of the language).
10
+ - ๐Ÿค– **Implementer** โ€” decided by Claude (the implementer): either fleshing out a mechanism the designer left open during the design conversation, or forced while building the proof-of-concept interpreter when the running code exposed a question the spec had left implicit.
11
+ - ๐Ÿ”ข **Designer's pick from offered options** โ€” Claude laid out the candidate choices; the designer selected one.
12
+
13
+ **Status legend:**
14
+
15
+ - โœ… **Accepted** โ€” the current status quo
16
+ - โช **Rewound** โ€” an overruled or superseded decision, or an alternative that was initially implemented and then later revised (see the referenced section).
17
+ - โŒ **Rejected alternative** โ€” considered, but rejected.
18
+ - ๐Ÿ’ญ **Hypothetical alternative** โ€” never seriously considered, doesn't fit into the language.
19
+ - ๐Ÿฉน **Remedied con** โ€” a listed drawback later fixed or mitigated.
20
+
21
+ ---
22
+
23
+ # 1. Values and data structures
24
+
25
+ ## 1.1 Three ingredients beyond atoms
26
+
27
+ ### Decisions
28
+
29
+ - ๐Ÿง‘ โœ… Besides atomic types (null, booleans, integers, floats, strings), the language has exactly three composite ingredients: arrays/lists, objects/maps and functions.
30
+
31
+ ### Alternatives
32
+
33
+ - ๐Ÿค– ๐Ÿ’ญ Add records/structs as distinct from maps.
34
+ - ๐Ÿค– ๐Ÿ’ญ Add tuples distinct from arrays.
35
+ - ๐Ÿค– ๐Ÿ’ญ Add a richer primitive set (dates, symbols, sets).
36
+
37
+ ### Pros
38
+
39
+ - Minimal concept count.
40
+ - Instant familiarity for anyone who knows JSON.
41
+ - A clean "JSON + functions" elevator pitch.
42
+
43
+ ### Cons
44
+
45
+ - No nominal types or tagged unions.
46
+ - Everything is structural, which can make large programs harder to keep disciplined.
47
+
48
+ ---
49
+
50
+ ## 1.2 JSON syntax for data
51
+
52
+ ### Decisions
53
+
54
+ - ๐Ÿง‘ โœ… Syntax for atomic types, arrays and objects is borrowed wholesale from JSON: arrays are `[...]`, objects are `{...}` with quoted string keys, atoms are JSON literals.
55
+
56
+ ### Alternatives
57
+
58
+ - ๐Ÿค– ๐Ÿ’ญ S-expressions (Lisp).
59
+ - ๐Ÿค– ๐Ÿ’ญ A bespoke literal syntax.
60
+ - ๐Ÿค– ๐Ÿ’ญ YAML-like indentation.
61
+
62
+ ### Pros
63
+
64
+ - Zero learning curve for data.
65
+ - Trivially serializable I/O.
66
+ - The language reads as data because it largely *is* data.
67
+
68
+ ### Cons
69
+
70
+ - Object keys must be quoted strings, which is verbose for record-like use.
71
+ - JSON's lack of bare identifiers is exploited (see 2.2), but JSON's other constraints carry over:
72
+ - Objects only support string keys
73
+ - ๐Ÿฉน No comments (Comments are added back as whole-line `#` comments โ€” see reference ยง2.4.)
74
+
75
+ ---
76
+
77
+ ## 1.3 Numeric int/float distinction
78
+
79
+ ### Decisions
80
+
81
+ - ๐Ÿง‘ โœ… Integers and floats are distinct kinds.
82
+ - ๐Ÿค– โœ… `divide` returns an integer when evenly divisible and a float otherwise.
83
+ - ๐Ÿค– โœ… `floor` returns an integer.
84
+ - ๐Ÿค– โœ… `equals` is exact.
85
+
86
+ ### Alternatives
87
+
88
+ - ๐Ÿค– ๐Ÿ’ญ A single number type (all floats, or arbitrary precision).
89
+ - ๐Ÿค– ๐Ÿ’ญ A full numeric tower.
90
+
91
+ ### Pros
92
+
93
+ - Matches JSON's practical number usage.
94
+ - Integer results stay integers.
95
+
96
+ ### Cons
97
+
98
+ - Two kinds to reason about.
99
+ - `Integer` vs. `Float` predicates can surprise (e.g. `2.0` is a `Float`, not an `Integer`).
100
+ - Equality across kinds is not automatic.
101
+
102
+ ---
103
+
104
+ ## 1.4 Member/index access failures yield `!`
105
+
106
+ ### Decisions
107
+
108
+ - ๐Ÿค– โœ… `x.key` on a missing key or non-object, and `x[i]` out of range or on a wrong type, yield `!` (not `null`).
109
+
110
+ ### Why the implementer decided this
111
+
112
+ - The spec flagged this as an open question made pressing by object-bundle access (`@lib.map`).
113
+ - Choosing `!` means a typo'd member (`@lib.fitler`) fails loudly rather than silently becoming `null` and propagating as a mystery later.
114
+
115
+ ### Alternatives
116
+
117
+ - ๐Ÿค– โŒ Return `null` for missing keys (treat objects as open maps).
118
+
119
+ ### Pros
120
+
121
+ - Catches typos and shape errors at the access site.
122
+ - Consistent with "`!` = something went wrong."
123
+
124
+ ### Cons
125
+
126
+ - Cannot use `x.maybeMissing` as a convenient "absent โ†’ null" probe.
127
+ - Callers wanting optionality must catch the `!`.
128
+
129
+ ---
130
+
131
+ # 2. Functions and errors
132
+
133
+ ## 2.1 Functions: one input, one output, ordered pattern-matching clauses
134
+
135
+ ### Decisions
136
+
137
+ - ๐Ÿง‘ โœ… Every function takes exactly one argument and returns one value.
138
+ - ๐Ÿง‘ โœ… A function literal is `(pattern => result, pattern => result, ...)`.
139
+ - ๐Ÿง‘ โœ… Clauses are tried top to bottom; the first match wins.
140
+
141
+ ### Alternatives
142
+
143
+ - ๐Ÿค– ๐Ÿ’ญ Multi-argument functions.
144
+ - ๐Ÿค– ๐Ÿ’ญ Unordered/guarded clause sets.
145
+ - ๐Ÿค– ๐Ÿ’ญ A separate `match`/`case` construct distinct from function definition.
146
+
147
+ ### Pros
148
+
149
+ - Application has a single uniform shape (see 2.3).
150
+ - Matching and dispatch are one mechanism.
151
+ - Multi-argument needs are met by passing arrays/objects, which are themselves first-class data.
152
+
153
+ ### Cons
154
+
155
+ - Verbose arithmetic and multi-argument calls.
156
+ - Currying must be written explicitly as nested functions.
157
+
158
+ ---
159
+
160
+ ## 2.2 Bare identifiers are "holes"
161
+
162
+ ### Decisions
163
+
164
+ - ๐Ÿง‘ โœ… Patterns and results are mirror images using the same names โ€” values captured by a pattern are re-inserted in the result.
165
+ - ๐Ÿค– โœ… A bare (unquoted) identifier is the binder/hole: it binds in a pattern and reads in an expression (Claude's choice to use JSON's one unused syntactic slot, rather than a sigil).
166
+
167
+ ### Alternatives
168
+
169
+ - ๐Ÿค– โŒ A sigil for binders (e.g. `$x`).
170
+ - ๐Ÿค– ๐Ÿ’ญ Explicit binding keywords.
171
+ - ๐Ÿค– ๐Ÿ’ญ Separate syntaxes for destructuring vs. construction.
172
+
173
+ ### Pros
174
+
175
+ - Exploits JSON's one unused syntactic slot.
176
+ - Produces a striking pattern/result symmetry.
177
+ - Destructuring reads as correspondence, not procedure.
178
+
179
+ ### Cons
180
+
181
+ - ๐Ÿฉน A name in pattern position silently shadows a built-in of the same name (e.g. a pattern `add` binds, it does not match the function `add`).
182
+ - No visual marker distinguishes a binder from a literal at a glance.
183
+
184
+ ---
185
+
186
+ ## 2.3 Application by pipe: `value | function`
187
+
188
+ ### Decisions
189
+
190
+ - ๐Ÿง‘ โœ… Function application is written `value | function`, left-associative.
191
+
192
+ ### Alternatives
193
+
194
+ - ๐Ÿค– ๐Ÿ’ญ Conventional `f(x)`.
195
+ - ๐Ÿค– ๐Ÿ’ญ Reverse-pipe `f <| x`.
196
+ - ๐Ÿค– ๐Ÿ’ญ Method-style `x.f()`.
197
+
198
+ ### Pros
199
+
200
+ - Pipelines read left-to-right like a sentence.
201
+ - Composes naturally with the one-argument rule.
202
+ - No call-syntax or arity.
203
+
204
+ ### Cons
205
+
206
+ - Unfamiliar to those expecting `f(x)`.
207
+ - Deeply nested non-linear data flow can require parentheses that reduce the pipeline's readability.
208
+
209
+ ---
210
+
211
+ ## 2.4 Refinement via `?`; types are predicates
212
+
213
+ ### Decisions
214
+
215
+ - ๐Ÿง‘ โœ… A pattern may be refined by appending `? predicate`
216
+ - ๐Ÿง‘ โœ… Predicate functions double as a runtime type system: the built-in "types" (`Integer`, `String`, โ€ฆ) are ordinary predicate functions, so `a ? @Integer` matches only integers (inspired by the Ruby gem "literal").
217
+ - ๐Ÿ”ข โœ… The `?` refinement may follow *any* pattern, not just a lone binder; the clause then matches iff the pattern matches structurally **and** the matched value piped into the predicate yields `true` (Claude offered a restrictive per-binder form vs. this permissive any-pattern form; the designer chose permissive).
218
+ - ๐Ÿง‘ โœ… The predicate is any function.
219
+
220
+ ### Alternatives
221
+
222
+ - ๐Ÿค– โŒ A dedicated type-annotation syntax.
223
+ - ๐Ÿค– โŒ Typed pattern keywords (`n: int`).
224
+ - ๐Ÿค– ๐Ÿ’ญ A separate static type system.
225
+ - ๐Ÿค– โŒ `if`-style guards with arbitrary boolean expressions.
226
+
227
+ ### Pros
228
+
229
+ - Unifies three things (structural matching, type checks, value guards) into one mechanism.
230
+ - The "type system" is user-extensible with ordinary functions.
231
+ - Nothing new to learn beyond `?`.
232
+
233
+ ### Cons
234
+
235
+ - All checking is dynamic; no static guarantees.
236
+ - A predicate is run at match time, with a cost.
237
+ - Expressing relational guards requires the "parent container" idiom (see 2.5).
238
+
239
+ ---
240
+
241
+ ## 2.5 No sibling scope in patterns; relational guards go on a parent
242
+
243
+ ### Decisions
244
+
245
+ - ๐Ÿง‘ โœ… All bindings in a clause are produced simultaneously.
246
+ - ๐Ÿง‘ โœ… A `?` predicate sees only the value matched by the pattern it is attached to, never a sibling binding.
247
+ - ๐Ÿ”ข โœ… To compare several captured values, attach the predicate to the enclosing container (Claude's "permissive" option; the designer selected it).
248
+
249
+ ### Alternatives
250
+
251
+ - ๐Ÿค– โŒ Left-to-right binding so later predicates can see earlier bindings (Claude leaned toward it; the designer rejected it).
252
+ - ๐Ÿค– ๐Ÿ’ญ Allowing predicates to reference the whole clause's bindings.
253
+
254
+ ### Pros
255
+
256
+ - Matching is a pure structural walk with no scope-threading.
257
+ - Predicates can be checked in any order or in parallel.
258
+ - The rule is trivially simple to state.
259
+
260
+ ### Cons
261
+
262
+ - Relational conditions (`a < b` across two bindings) need the slightly awkward "attach `?` to `[a, b]` and re-destructure inside the predicate" idiom.
263
+
264
+ ---
265
+
266
+ ## 2.6 The error mode `!`, distinct from `null`
267
+
268
+ ### Decisions
269
+
270
+ - ๐Ÿง‘ โœ… Introduce a distinct error mode `!`, separate from `null`.
271
+ - ๐Ÿง‘ โœ… `null` = legitimate absence; `!` = failure.
272
+ - ๐Ÿง‘ โœ… A function is made *strict* by ending with `_ => !` (error on no match) and is otherwise *lenient* (returns `null` on no match).
273
+ - ๐Ÿง‘ โœ… Total predicates end with `_ => false`.
274
+ - ๐Ÿค– โœ… Built-in operations return `!` on bad input; built-in predicates return `false`.
275
+ - ๐Ÿง‘ โœ… The form of `!` (always carrying a payload) is fixed by 2.8.
276
+ - ๐Ÿค– โœ… `!` matches **only** error patterns (not `_`, not a binder).
277
+ - ๐Ÿ”ข โœ… Applying any function to `!` returns `!` unless that function has a clause whose pattern catches it (Claude offered auto-propagation-with-a-`catch`-builtin vs. this "matching `!` is ordinary" option; the designer chose the latter).
278
+ - ๐Ÿค– โœ… Error propagation is thus a property of application itself, independent of strictness โ€” "strict" means only "error on no match," and propagation is automatic.
279
+
280
+ ### Alternatives
281
+
282
+ - ๐Ÿง‘ โช Overload `null` for both meanings (the original "no match โ†’ `null`" rule, superseded once `!` split absence from failure).
283
+ - ๐Ÿค– โŒ Make the *pipe operator* short-circuit on `!` with a dedicated `catch` built-in as the only handler (rejected: needs new mechanism; the existing pattern-matching machinery already expresses catches via an error pattern).
284
+ - ๐Ÿค– โช Couple propagation to strictness so that only strict functions propagate (initially accepted as "a strict function is exactly one that propagates," then revised in implementation: `_` rejecting `!` and `_ => !` re-emitting `!` contradict each other, so propagation was decoupled from strictness and made a property of application).
285
+
286
+ ### Pros
287
+
288
+ - `Result`/exception-style short-circuiting with no new syntax.
289
+ - Absence and failure are cleanly separated.
290
+ - Strictness is opt-in per function.
291
+
292
+ ### Cons
293
+
294
+ - `_` does not mean "literally anything" (it excludes `!`), a subtle asymmetry.
295
+ - ๐Ÿฉน An uncaught error can travel far from its origin, which can make debugging harder (mitigated by `FUSION_DEBUG`, by the payload from 2.8, and by potential future diagnostics tracked in the roadmap).
296
+
297
+ ---
298
+
299
+ ## 2.7 Non-exhaustive match: `null` for normal inputs, propagate for errors
300
+
301
+ ### Decisions
302
+
303
+ - ๐Ÿง‘ โœ… If no clause matches and the input is not an error, the result is `null`.
304
+ - ๐Ÿง‘ โœ… Strictness (`_ => !`) is opt-in.
305
+ - ๐Ÿง‘ โœ… If the input is an error and no clause matched, the original error propagates (it is never silently turned into `null`).
306
+ - ๐Ÿง‘ โœ… The split matters because a function with error clauses matching *some* error shapes (e.g. `(!42 => "got 42", _ => "ok")`) and receiving an error of a different shape must still propagate that error โ€” anything else would silently swallow failures that no clause acknowledged.
307
+
308
+ ### Alternatives
309
+
310
+ - ๐Ÿค– โŒ Make non-exhaustive matching an error by default (strict-by-default), with leniency opt-in.
311
+ - ๐Ÿค– โช Unify "no match" handling so that *all* non-matches return `null` regardless of input kind (the interpreter initially did this and swallowed unmatched errors to `null`; revised so partial error handlers preserve the propagation model).
312
+
313
+ ### Pros
314
+
315
+ - Forgiving during exploration and prototyping.
316
+ - Base cases read naturally for non-error inputs.
317
+ - Errors are *never* silently swallowed, so a partially-matching error handler still preserves the original failure for diagnosis.
318
+
319
+ ### Cons
320
+
321
+ - A typo'd or incomplete function silently yields `null` on non-error inputs, which can hide bugs several layers deep.
322
+ - The safer strict behavior must be remembered and added.
323
+
324
+ ---
325
+
326
+ ## 2.8 Payloaded errors
327
+
328
+ ### Decisions
329
+
330
+ - ๐Ÿง‘ โœ… Every error is `!` followed by a **payload**, which may be any Fusion value (`!42`, `!"divide by zero"`, `!{"kind":"missing_key","key":"id"}`, `!null`).
331
+ - ๐Ÿง‘ โœ… Bare `!` in expression position is shorthand for `!null`.
332
+ - ๐Ÿง‘ โœ… In expression position, `!expr` is a prefix operator that wraps a value as an error; in pattern position, `!pat` matches an error and destructures its payload (bare `!` matches any error without binding; `!_` does the same but admits a `?` predicate).
333
+ - ๐Ÿง‘ โœ… Propagation preserves the *same* error (payload intact), so by the time you reach a catch site you still know what happened.
334
+ - ๐Ÿง‘ โœ… The CLI prints the payload (as JSON) to stderr on failure and exits `1`, leaving stdout empty.
335
+ - ๐Ÿง‘ โœ… **Errors propagate uniformly โ€” they are not values.** At any moment of execution there is either a value or an error in motion, never both. Built-ins (including `@equals` and the type predicates), array and object literals, `?` predicates, and the function-value position of a pipe all propagate an encountered error. The only way to do anything with an error besides letting it propagate is to catch it in an `!pat` clause, which yields a normal value (the payload).
336
+ - ๐Ÿง‘ โœ… **No nested errors.** When `!expr` is evaluated and `expr` itself produces an error, that inner error propagates and the outer `!` is a no-op. This preserves the "never more than one error simultaneously" invariant โ€” there is no `!!` value, ever.
337
+ - ๐Ÿง‘ โœ… **Partial matching propagates the unmatched error.** A function with error clauses that match *some* error shapes (e.g. `!42 => ...`) but not the one it receives (e.g. `!"oops"`) propagates the unmatched error rather than turning it into `null`. The "no match โ†’ null" lenient default from 2.7 applies only to non-error inputs.
338
+ - ๐Ÿง‘ โœ… **`!pat` is a top-level prefix in the clause grammar.** `!` is a prefix on the *clause pattern*, not on any sub-pattern: array elements, object members, and the payload of another `!` all recurse into the non-`!` pattern production. This grammar shape simultaneously enforces two things with no special-case parsing flag: (i) nested error patterns (`[!a, b]`, `{"err": !x}`, `!!42`, `!{"k": !v}`) are syntax errors, matching the runtime invariant that errors never sit inside other values; and (ii) `!pat ? pred` parses as `!(pat ? pred)`, so the `?` binds *inside* the `!`. The runtime payoff is that the predicate of `!a ? pred` naturally sees the payload, with no special case needed in `PGuard`.
339
+ - ๐Ÿง‘ โœ… **Predicate-errors bubble up to the function level.** If a `?` predicate evaluates to an error (it crashed, or it was itself an error value), that error becomes the function's result immediately, without trying later clauses. The alternative โ€” treating a predicate-error as "no match" and continuing โ€” would silently hide bugs in the predicate.
340
+
341
+ ### Alternatives
342
+
343
+ - ๐Ÿง‘ โช Keep `!` opaque with no payload (the original error model, superseded because it was too hard to debug).
344
+ - ๐Ÿค– ๐Ÿ’ญ Use a `Result`-style two-variant `Ok | Err` (hypothetical: needs new machinery; payloaded errors with propagation give the same ergonomics with two rules).
345
+ - ๐Ÿค– โŒ Make payloads always strings (Claude's first payload sketch, e.g. `!"divide by zero"`; rejected because built-in mechanics like missing keys benefit from structured payloads).
346
+ - ๐Ÿค– โช Make errors first-class values that can be stored in collections, compared with `@equals`, and inspected by predicates (Claude implemented this reading; the designer corrected it because it creates carve-outs in propagation that contradict the "never more than one error" invariant).
347
+ - ๐Ÿค– โช Parse `!pat ? pred` as `(!pat) ? pred` so the predicate refines the whole error rather than the payload (Claude parsed it this way; the designer corrected it to `!(pat ? pred)` so the predicate sees the payload, matching its sibling binders).
348
+
349
+ ### Pros
350
+
351
+ - Vastly improved debuggability โ€” a propagated error tells you both *that* something failed and *what*.
352
+ - The catch site can dispatch on the error kind (`(!{"kind":"missing_key"} => ..., !msg => !msg)`).
353
+ - Construction and matching are syntactically symmetric (`!42` builds on the right of `=>`, matches on the left).
354
+ - Propagation remains uniform, with no carve-outs to remember.
355
+
356
+ ### Cons
357
+
358
+ - The payload format is part of the language's surface โ€” built-ins use string payloads (`"divide: division by zero"`) while runtime mechanics use structured object payloads (`{"kind": ...}`), an inconsistency tracked in the roadmap.
359
+ - A payload that itself contains sensitive data becomes part of the program's stderr stream.
360
+ - The bare-`!`-means-`!null` rule preserves a simple expression form but means a careless `_ => !` clause gives a maximally unhelpful error.
361
+ - Inspecting an error's payload requires a small catch-and-rebind (`(!a => a)`) rather than direct comparison.
362
+
363
+ ---
364
+
365
+ # 3. @ references
366
+
367
+ ## 3.1 A file contains exactly one value
368
+
369
+ ### Decisions
370
+
371
+ - ๐Ÿง‘ โœ… A `.fsn` file contains exactly one expression, which is its value.
372
+ - ๐Ÿง‘ โœ… A file is *executable* if that value is a function; the runtime computes `STDIN | thatFunction`.
373
+ - ๐Ÿง‘ โœ… No top-level statement list, no top-level bindings.
374
+
375
+ ### Alternatives (all earlier drafts, then dropped)
376
+
377
+ - ๐Ÿง‘ โช A program as a list of `name = value` bindings executed top-to-bottom.
378
+ - ๐Ÿค– โช Bindings plus a trailing "main" expression with mutually-recursive (`letrec`) scope.
379
+
380
+ ### Pros
381
+
382
+ - The outermost layer is the same kind of thing as every inner layer (a value).
383
+ - Eliminates a whole second sub-language and its scoping rules.
384
+ - Makes the module system fall out for free (see 3.2).
385
+
386
+ ### Cons
387
+
388
+ - No place for local definitions.
389
+ - Arithmetic/glue code can become many tiny files.
390
+
391
+ ---
392
+
393
+ ## 3.2 File references as the module system
394
+
395
+ ### Decisions
396
+
397
+ - ๐Ÿง‘ โœ… `@a` evaluates to the value in `a.fsn`; `@dir/a` into a subdirectory; `@../a` up a directory.
398
+ - ๐Ÿง‘ โœ… A bare `@` is the current file.
399
+ - ๐Ÿง‘ โœ… This is the entire module system; there is no `import` primitive, and resolution is relative to the referencing file.
400
+ - ๐Ÿง‘ โœ… Recursion is written with a bare `@` (self-reference).
401
+ - ๐Ÿง‘ โช The full resolution rules (including how built-ins and the standard library now share this namespace) were revised later โ€” see 3.6.
402
+
403
+ ### Alternatives
404
+
405
+ - ๐Ÿค– โŒ An explicit `import`/`use` construct with a namespace table.
406
+ - ๐Ÿค– ๐Ÿ’ญ Content-addressed or URL-based imports.
407
+ - ๐Ÿค– ๐Ÿ’ญ A single global namespace.
408
+
409
+ ### Pros
410
+
411
+ - A file is a value, so importing is just referencing a value โ€” one mechanism covers top-level structure, modules, and stdlib delivery.
412
+ - The directory tree is the namespace.
413
+ - Relocatable like Node relative `require`.
414
+
415
+ ### Cons
416
+
417
+ - Couples module identity to filesystem layout.
418
+ - Deep relative paths can be unwieldy.
419
+ - Reaching outside the project (`@../../../x`) is possible and needs runtime sandboxing (a runtime concern).
420
+
421
+ ---
422
+
423
+ ## 3.3 References are lazy and memoized
424
+
425
+ ### Decisions
426
+
427
+ - ๐Ÿค– โœ… A reference resolves when used, not when its file loads, and each path is evaluated once per run and cached.
428
+
429
+ ### Why it matters (confirmed in implementation)
430
+
431
+ - ๐Ÿค– Laziness is what makes self- and mutual recursion possible: an eager resolver would loop forever resolving a file that references itself. The interpreter confirmed self-recursion (a bare `@` meaning "this file") and cross-file mutual recursion (`@even`/`@odd`) both work precisely because resolution is deferred to application time.
432
+
433
+ ### Alternatives
434
+
435
+ - ๐Ÿค– โŒ Eager resolution at load (incompatible with self-reference).
436
+ - ๐Ÿค– โŒ No caching (re-evaluates shared dependencies redundantly).
437
+
438
+ ### Pros
439
+
440
+ - Enables recursion with no special construct.
441
+ - Unused references never load.
442
+ - Shared/diamond dependencies load once.
443
+
444
+ ### Cons
445
+
446
+ - Evaluation order is less obvious.
447
+ - ๐Ÿฉน Data cycles are possible (handled โ€” see 3.4).
448
+
449
+ ---
450
+
451
+ ## 3.4 Data-cycle handling
452
+
453
+ ### Decisions
454
+
455
+ - ๐Ÿค– โœ… A non-productive data cycle (files whose values reference each other as data, not through a function boundary) yields an error at the point of the cyclic self-reference.
456
+ - ๐Ÿค– โช The surrounding data structure is preserved. Example: `cyclicA = [1, @cyclicB]`, `cyclicB = [2, @cyclicA]` evaluates to `[1, [2, !]]`. Superseded by 2.8 as errors now immediately bubble to the top of each data structure.
457
+
458
+ ### Why the implementer decided this
459
+
460
+ - The spec said only "detect a cycle โ†’ `!`."
461
+ - The running thunk-forcing logic naturally produced something more precise and more useful: the error lands exactly where the cycle closes, and the rest of the value survives.
462
+
463
+ ### Alternatives
464
+
465
+ - ๐Ÿค– โŒ Blanket top-level `!` for the whole value (less informative).
466
+ - ๐Ÿค– โŒ Allow cycles as lazy infinite data (would require a lazy/streaming value model).
467
+
468
+ ### Pros
469
+
470
+ - Maximally informative failure.
471
+ - Localizes the problem.
472
+
473
+ ### Cons
474
+
475
+ - ๐Ÿฉน A partially-`!` data structure can be surprising if not expected.
476
+
477
+ ---
478
+
479
+ ## 3.5 Object-bundle access `@file.key` needs no new syntax
480
+
481
+ ### Decisions
482
+
483
+ - ๐Ÿง‘ โœ… Accessing a function bundled in a file-object, `@lib.map`, requires no new grammar: it is `(@lib)` (a primary) followed by `.map` (postfix member access), and `.` binds tighter than `|` so `xs | @lib.map` parses correctly.
484
+ - ๐Ÿง‘ โœ… A bundled function refers to itself and its siblings through `@.map` (a bare `@` for the current file, then a `.member` access), so it never has to name its own file and stays relocatable when the file is renamed.
485
+
486
+ ### Pros
487
+
488
+ - Two library-organization styles (directory of files, or one object file) with no extra syntax.
489
+
490
+ ### Cons
491
+
492
+ - The two styles are not equivalent.
493
+ - Bundling loads the whole file to reach one member (coarser load granularity), trading that for cohesion.
494
+
495
+ ---
496
+
497
+ ## 3.6 Unified `@` namespace with per-file shadowing
498
+
499
+ ### Decisions
500
+
501
+ All access goes through `@`:
502
+
503
+ - ๐Ÿง‘ โœ… **Built-ins require `@`.** `@add`, `@Integer`, etc. A bare identifier is *only* a pattern hole; it never denotes a built-in. Built-ins cannot be shadowed by a clause's bindings โ€” they live in a different namespace entirely.
504
+ - ๐Ÿง‘ โœ… **The standard library has no prefix.** `@map` reaches it directly.
505
+ - ๐Ÿง‘ โœ… **An `@name` reference (without leading `../`) resolves: sibling file โ†’ built-in โ†’ stdlib file โ†’ error.** First match wins. Consequently siblings can shadow a built-in or a stdlib function, but only for files in that directory (never globally).
506
+ - ๐Ÿง‘ โœ… **Built-in/stdlib fallback is gated on `../`, not on `/`.** Downward paths (`@dir/a`, `@math/sqrt`) remain eligible for the built-in/stdlib fallback; only upward paths (`@../a`) are file-only and never fall back.
507
+ - ๐Ÿง‘ โœ… **A bare `@`** (nothing after it) means the current file โ€” recursion is written this way rather than by repeating the file's own name.
508
+ - ๐Ÿง‘ โœ… **`@ENV`** is a built-in evaluating to an object of environment variables (all string values, no parsing); read with member access (`@ENV.CI`).
509
+ - ๐Ÿง‘ โœ… **`@load`** is a built-in taking a filename **verbatim** (no `.fsn` appended), resolved relative to the referencing file, for runtime/non-identifier filenames. Both `@ENV` and `@load` resolve in the `@name` chain, so both are shadowable by a sibling file of that name.
510
+
511
+ ### Why a single uniform chain
512
+
513
+ - Every bare `@name` follows one precedence order (sibling โ†’ builtin โ†’ stdlib), so there are no reserved names to remember and no parser carve-outs.
514
+ - `ENV` and `load` live in the builtin tier like everything else.
515
+ - Gating fallback on `../` rather than on the presence of any `/` keeps downward paths (`@math/sqrt`) eligible for the stdlib, which is what stdlib subpackaging needs.
516
+
517
+ ### Alternatives
518
+
519
+ - ๐Ÿค– โช Keep built-ins as bare globals (the prototype's original scheme, revised so built-ins require `@` and can't be shadowed by bindings).
520
+ - ๐Ÿค– โช Add an `@std/` prefix for the standard library (used in the early prototype, e.g. `@std/map`; the designer removed it so stdlib has no prefix).
521
+ - ๐Ÿค– โŒ Reserve `@ENV`/`@load` as unshadowable (Claude raised it as a question; the designer chose to make both shadowable like any built-in).
522
+ - ๐Ÿค– โช Gate fallback on any `/` (Claude's first implementation; the designer corrected it to gate on `../` only, so downward stdlib subdirectories still work).
523
+
524
+ ### Pros
525
+
526
+ - One uniform access sigil for files, built-ins, stdlib, self, env, and dynamic load.
527
+ - Built-ins cannot be accidentally shadowed by bindings.
528
+ - Safe, *per-directory* shadowing of built-ins/stdlib.
529
+ - Downward stdlib packages (`@math/...`) work.
530
+
531
+ ### Cons
532
+
533
+ - Built-ins are verbose (`@add` everywhere).
534
+ - Shadowing is invisible at the call site (whether `@map` is yours or the stdlib's depends on directory contents).
535
+ - A bare word that *looks* like a function reference is silently just a hole (reading an unbound one yields an error).
536
+
537
+ ---
538
+
539
+ # 4. Runtime and CLI
540
+
541
+ ## 4.1 Runtime I/O contract
542
+
543
+ ### Decisions
544
+
545
+ - ๐Ÿง‘ โœ… Read stdin as JSON โ†’ value `v`; compute `v | program`; print the result as JSON.
546
+ - ๐Ÿง‘ โœ… A final `!` produces a nonzero exit code.
547
+ - ๐Ÿค– โœ… Empty stdin is treated as `null`.
548
+ - ๐Ÿค– โœ… Non-JSON stdin yields `!`.
549
+
550
+ ### Alternatives
551
+
552
+ - ๐Ÿค– โŒ NDJSON/streaming input mapping the program over each line (deferred).
553
+ - ๐Ÿค– โช A richer error report on stderr instead of a bare nonzero exit.
554
+
555
+ ### Pros
556
+
557
+ - Fusion programs are first-class Unix filters.
558
+ - `!` maps onto exit status for free.
559
+
560
+ ### Cons
561
+
562
+ - No streaming; whole input must be buffered and parsed.
563
+ - ๐Ÿฉน A bare `!` with nonzero exit gives little diagnostic detail (mitigated by `FUSION_DEBUG`).
564
+
565
+ ---
566
+
567
+ # 5. Misc
568
+
569
+ ## 5.1 No operator sugar (deferred)
570
+
571
+ ### Decisions
572
+
573
+ - ๐Ÿง‘ โœ… No infix `+ - * / == < && โ€ฆ`.
574
+ - ๐Ÿง‘ โœ… Arithmetic, comparison, and boolean operations are built-in functions applied to a pair, e.g. `[a, b] | @add`.
575
+ - ๐Ÿง‘ โœ… Sugar is explicitly deferred, not rejected.
576
+
577
+ ### Alternatives
578
+
579
+ - ๐Ÿค– โช Provide infix operators as sugar desugaring to the built-ins immediately.
580
+
581
+ ### Pros
582
+
583
+ - Keeps the core grammar tiny and uniform while semantics are being settled.
584
+ - Everything is visibly "just application."
585
+
586
+ ### Cons
587
+
588
+ - Arithmetic-heavy code is verbose and harder to read (`[n, [n, 1] | @subtract | @fact] | @multiply` vs. `n * fact(n-1)`).
589
+
590
+ ---
591
+
592
+ ## 5.2 Standard library as one-function-per-file `.fsn`
593
+
594
+ ### Decisions
595
+
596
+ - ๐Ÿง‘ โœ… The standard library is a directory of `.fsn` files reached via `@name` (the designer's file-reference scheme โ€” "this should also solve how we build our standard library").
597
+ - ๐Ÿค– โœ… Each stdlib file is typically one function written in Fusion; only true primitives that cannot be written in Fusion are built into the interpreter.
598
+
599
+ ### Alternatives
600
+
601
+ - ๐Ÿค– ๐Ÿ’ญ A single bundled stdlib object/file.
602
+ - ๐Ÿค– โŒ Built-ins for everything common (Claude argued against this: include a built-in only if it can't be written in Fusion).
603
+
604
+ ### Pros
605
+
606
+ - Fine-grained loading (use one function, load one file).
607
+ - Dogfoods the language.
608
+ - Proves expressiveness (if `map` can't be written in Fusion, that's a red flag).
609
+
610
+ ### Cons
611
+
612
+ - Many small files.
613
+
614
+ ---
615
+
616
+ ## 5.3 Built-in primitive set (Tier 0)
617
+
618
+ ### Decisions
619
+
620
+ - ๐Ÿค– โœ… Only things that can't be built in Fusion itself become a builtin. Other frequently used functions become part of the standard library.
621
+ - ๐Ÿค– โœ… The interpreter provides these builtins:
622
+ - arithmetic (`add`, `subtract`, `multiply`, `divide`, `mod`, `negate`, `floor`);
623
+ - comparison (`equals`, `lessThan`);
624
+ - boolean (`and`, `or`, `not`);
625
+ - bridges (`length`, `concat`, `chars`, `join`, `toString`, `parseNumber`, `keys`, `values`);
626
+ - predicates (`Integer`, `Float`, `Number`, `String`, `Boolean`, `Array`, `Object`, `Null`).
627
+ - ๐Ÿค– โœ… `keys` must be a builtin: pattern matching can pull *known* object keys but cannot enumerate *unknown* ones, so iterating an object of unknown shape is impossible without it.
628
+
629
+ ### Alternatives
630
+
631
+ - ๐Ÿค– โŒ Derive `lessThan`'s siblings as built-ins too (chose to leave `lessEq`/`greaterThan`/etc. to the library).
632
+ - ๐Ÿค– โŒ Omit `values` (derivable from `keys`).
633
+
634
+ ### Pros
635
+
636
+ - Small, principled core.
637
+ - Clear "can't be written in Fusion" inclusion test.
638
+
639
+ ### Cons
640
+
641
+ - Boundary cases (`floor`, `values`) are judgment calls.
642
+ - The Tier 1 library that would sit on top is only partially populated in the prototype.
643
+
644
+ ---
645
+
646
+ ## 5.4 Whole-line `#` comments
647
+
648
+ ### Decisions
649
+
650
+ - ๐Ÿง‘ โœ… Comments are whole lines only: a line is a comment iff its first non-whitespace character is `#`. There are no inline or trailing comments.
651
+ - ๐Ÿง‘ โœ… Shebang lines (`#!/usr/bin/env fusion`) are supported, but need no special case, since a `#!` line is already a comment by the rule above.
652
+ - ๐Ÿ”ข โœ… Raw newlines inside string literals are forbidden (write `\n` instead), matching strict JSON. Claude flagged that the easy-strip guarantee depends on this.
653
+ - ๐Ÿ”ข โœ… The previous `// line` and `/* block */` syntax is removed, not kept as an alias
654
+
655
+ ### Why the implementer flagged the string constraint
656
+
657
+ - The headline goal was "comments can be stripped without understanding the grammar." That holds for a per-line stripper (`grep -v '^[[:space:]]*#'`) **only if** a `#` at line-start can never be inside a string โ€” i.e. strings cannot span physical lines. The old lexer accepted raw newlines in strings, which would have silently broken the guarantee, so the constraint was made explicit.
658
+
659
+ ### Alternatives
660
+
661
+ - ๐Ÿค– โŒ Keep inline/trailing comments (e.g. `x | f # note`). Rejected: a trailing comment reintroduces the string-vs-comment ambiguity (`"#"` in a string), defeating grammar-free stripping.
662
+ - ๐Ÿค– โŒ Keep `//` and `/* */` as aliases. Rejected for the same reason โ€” `//` inside a string (`"http://โ€ฆ"`) breaks naive stripping.
663
+ - ๐Ÿค– ๐Ÿ’ญ Allow multi-line strings and have the stripper track string state. Rejected: that is exactly the "understand the grammar" cost the design set out to avoid.
664
+
665
+ ### Pros
666
+
667
+ - Comments are strippable by a one-line filter with no parser, and the rule is trivial to state.
668
+ - Shebang support falls out for free; the lexer treats `#!` as an ordinary comment.
669
+ - Fits the "functional JSON / Unix filter" idiom (shell, Python, YAML, TOML, Make all use `#`).
670
+
671
+ ### Cons
672
+
673
+ - No way to annotate a single token mid-line; an explanatory comment must occupy its own line above the code.
674
+ - A breaking change from the earlier `//` / `/* */` syntax (acceptable at this Alpha stage).