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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +79 -0
- data/Rakefile +8 -0
- data/docs/index.md +34 -0
- data/docs/lang/design.md +674 -0
- data/docs/lang/roadmap.md +97 -0
- data/docs/user/explanation.md +157 -0
- data/docs/user/how-to-guides.md +205 -0
- data/docs/user/reference.md +505 -0
- data/docs/user/tutorial.md +338 -0
- data/examples/double.fsn +4 -0
- data/examples/factorial.fsn +6 -0
- data/examples/first.fsn +4 -0
- data/examples/fizzbuzz.fsn +15 -0
- data/examples/palindrome.fsn +8 -0
- data/exe/fusion +57 -0
- data/lib/fusion/version.rb +3 -0
- data/lib/fusion.rb +1140 -0
- data/stdlib/map.fsn +4 -0
- data/stdlib/math/square.fsn +1 -0
- data/stdlib/range.fsn +4 -0
- metadata +67 -0
data/docs/lang/design.md
ADDED
|
@@ -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).
|