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
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Fusion roadmap
|
|
2
|
+
|
|
3
|
+
Tracked future work and open questions. Decisions that have already been made
|
|
4
|
+
live in [design.md](./design.md); this file is only for things still ahead.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Ergonomics: the most-wanted improvements
|
|
9
|
+
|
|
10
|
+
**Operator sugar (planned).** Reintroduce infix `+ - * / % == != < <= > >= && || !`
|
|
11
|
+
and string `++`, desugaring to the existing built-ins over pairs. Pure ergonomics,
|
|
12
|
+
no semantic change. This is the single biggest readability win available and was
|
|
13
|
+
always intended. Open question: exact precedence table and how it interleaves with
|
|
14
|
+
`|` and `=>`.
|
|
15
|
+
|
|
16
|
+
**`@`-namespace resolution polish.** Decide on project-root confinement
|
|
17
|
+
(sandboxing) for `@../` escapes; consider a configurable standard-library search
|
|
18
|
+
path; consider tooling to surface *which* target a given `@name` resolves to in
|
|
19
|
+
a directory, since shadowing is invisible at the call site.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 2. Error model
|
|
24
|
+
|
|
25
|
+
**Payload shape consistency** *(open)*. The payload *shape* is inconsistent:
|
|
26
|
+
built-ins use bare strings (`"divide: division by zero"`) while runtime
|
|
27
|
+
mechanics use structured objects (`{"kind":"missing_key","key":"foo"}`). The
|
|
28
|
+
string form is human-friendlier; the structured form is machine-friendlier
|
|
29
|
+
(catchable via `!{"kind": k}`). Three plausible resolutions:
|
|
30
|
+
|
|
31
|
+
- (a) all errors get structured payloads with a `kind` and an optional `message`;
|
|
32
|
+
- (b) all errors get human strings, and structured matching is left to user code;
|
|
33
|
+
- (c) keep both, document the rule that built-in operations use strings and
|
|
34
|
+
runtime mechanics use objects.
|
|
35
|
+
|
|
36
|
+
This is a small but irreversible decision (it shapes how catch clauses are
|
|
37
|
+
written) and should be decided before any external code grows that depends on
|
|
38
|
+
the current shapes.
|
|
39
|
+
|
|
40
|
+
**Better diagnostics.** `FUSION_DEBUG` exists for file/parse errors; extend
|
|
41
|
+
principled diagnostics to runtime error origins (where did this error first
|
|
42
|
+
arise?). One option: attaching a source position to the payload (an extra
|
|
43
|
+
`"at": "file.fsn:L:C"` field) when `FUSION_DEBUG` is set.
|
|
44
|
+
|
|
45
|
+
**Stack traces** *(deferred)*. A propagated error tells you what happened, but
|
|
46
|
+
not the chain of function applications it passed through. A capped trace
|
|
47
|
+
(last N frames, accessible as an extra payload field, opt-in via env) would
|
|
48
|
+
help in deep pipelines.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 3. Standard library completion
|
|
53
|
+
|
|
54
|
+
Populate Tier 1 (written in Fusion): `filter`, `reduce`/`fold`, `reverse`,
|
|
55
|
+
`head`, `tail`, `last`, `init`, `take`, `drop`, `zip`, `flatten`, `member`,
|
|
56
|
+
`find`, `all`, `any`, `count`; comparison derivatives `lessEq`, `greaterThan`,
|
|
57
|
+
`greaterEq`, `notEquals`; object helpers `entries`, `get`, `set`, `merge`; an
|
|
58
|
+
`if` helper. This is also the best stress test of whether the language is
|
|
59
|
+
pleasant to *write* in, not just to implement.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 4. Runtime and tooling
|
|
64
|
+
|
|
65
|
+
- **Streaming I/O** (NDJSON): map a program over a stream of JSON values.
|
|
66
|
+
- **A faster implementation** once semantics are frozen.
|
|
67
|
+
|
|
68
|
+
## 5. Open semantic questions to settle
|
|
69
|
+
|
|
70
|
+
- Numeric tower: keep int/float split, or move to a single number type /
|
|
71
|
+
arbitrary precision? Affects `divide`, `floor`, `equals`, and the
|
|
72
|
+
`Integer`/`Float` predicates.
|
|
73
|
+
- Function equality: `equals` on two functions — always `false`, or an error?
|
|
74
|
+
(Function equality is undecidable beyond trivial identity.)
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## 6. Bigger experiments
|
|
79
|
+
|
|
80
|
+
**Destructuring functions (homoiconicity).** Treat a function as a list of
|
|
81
|
+
`(pattern, output)` clause-pairs and pattern-match on it, enabling macros and
|
|
82
|
+
function transformers with the same matching machinery. The clean path is
|
|
83
|
+
explicit, opt-in reflection (`reflect : function → data`,
|
|
84
|
+
`reify : data → function`) representing patterns as reflective AST objects, so
|
|
85
|
+
normal code keeps functions opaque and "three ingredients" intact. High payoff
|
|
86
|
+
(metaprogramming), moderate disruption.
|
|
87
|
+
|
|
88
|
+
**Running functions backwards (relational mode).** Given an output, find an
|
|
89
|
+
input — unification and search, à la Prolog/miniKanren. Clean only for
|
|
90
|
+
invertible functions; hopeless for many-to-one. Would change Fusion from
|
|
91
|
+
functional to relational and needs backtracking search. The most exciting and
|
|
92
|
+
most disruptive possible direction; best pursued as a separate mode or sibling
|
|
93
|
+
project rather than folded into the core.
|
|
94
|
+
|
|
95
|
+
**A static checker.** Because "types" are predicates, an optional static layer
|
|
96
|
+
could attempt to verify predicate-guarded clauses and exhaustiveness without
|
|
97
|
+
changing the dynamic semantics. Speculative.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Explanation
|
|
2
|
+
|
|
3
|
+
*This is an understanding-oriented discussion: it provides context and answers "why?".
|
|
4
|
+
This article may hold opinions and approach the subject from several angles. For the
|
|
5
|
+
formal decision ledger (who decided what, alternatives, trade-offs) see the
|
|
6
|
+
[Design documentation](../lang/design.md).*
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## What Fusion is, in one breath
|
|
11
|
+
|
|
12
|
+
Fusion is "functional JSON": take JSON — its atoms, arrays, and objects — and add one
|
|
13
|
+
more ingredient, the function. Keep the count of concepts brutally small. From those
|
|
14
|
+
pieces, recover everything a small language needs: branching, looping, types,
|
|
15
|
+
modules, and error handling. The whole design is an exercise in *not adding things*
|
|
16
|
+
and discovering that the pieces you already have are enough.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Why only three ingredients
|
|
21
|
+
|
|
22
|
+
Most languages accrete features: statements, loops, conditionals, classes,
|
|
23
|
+
exceptions, a type system, a module system, each with its own syntax. Fusion's bet is
|
|
24
|
+
that almost all of that is unnecessary if your three composite ingredients — arrays,
|
|
25
|
+
objects, functions — are chosen well and made to compose.
|
|
26
|
+
|
|
27
|
+
The discipline pays a recurring dividend. Again and again, a feature that would
|
|
28
|
+
normally be its own language construct turns out to be expressible with what already
|
|
29
|
+
exists:
|
|
30
|
+
|
|
31
|
+
- An **`if`** is a function with `true` and `false` clauses.
|
|
32
|
+
- A **`for` loop** is recursion that pattern-matches a list down to `[]`.
|
|
33
|
+
- A **type** is a predicate function attached with `?`.
|
|
34
|
+
- A **module** is a file, and an import is a reference to that file's value.
|
|
35
|
+
- **Error propagation** is just how application treats the error value.
|
|
36
|
+
|
|
37
|
+
None of these required new syntax. That is the aesthetic core of the language: when
|
|
38
|
+
you feel the urge to add a construct, look harder at the three you have.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Why functions take exactly one argument
|
|
43
|
+
|
|
44
|
+
This looks like a severe restriction and is actually a simplification that makes
|
|
45
|
+
everything else line up. With exactly one input and one output:
|
|
46
|
+
|
|
47
|
+
- **Application has one shape:** `value | function`. There is no argument list, no
|
|
48
|
+
call syntax, no arity to track. A pipeline `a | f | g | h` reads like a sentence.
|
|
49
|
+
- **Multi-argument needs are met by data:** pass an array `[a, b]` or an object
|
|
50
|
+
`{"f": ..., "xs": ...}`. The "arguments" become a value you can also store, inspect,
|
|
51
|
+
and destructure — there's no separate notion of "argument tuple."
|
|
52
|
+
- **Pattern matching has one job:** match the single input. A function's clauses are
|
|
53
|
+
just alternative shapes that one input might have.
|
|
54
|
+
|
|
55
|
+
The cost is verbosity in arithmetic (`[a, b] | @add` instead of `a + b`) and a little
|
|
56
|
+
ceremony for multi-argument library functions. The first is a candidate for later
|
|
57
|
+
syntactic sugar; the second is mild. In exchange, the evaluation model is almost
|
|
58
|
+
trivially simple, which is exactly what you want in a language meant to be small.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Why bare words are "holes"
|
|
63
|
+
|
|
64
|
+
The keystone trick is that a bare identifier means *bind* in a pattern and *read* in
|
|
65
|
+
an expression. This works because JSON has no bare identifiers — strings are quoted,
|
|
66
|
+
so an unquoted word is an unused syntactic slot. Fusion claims that slot for local
|
|
67
|
+
variables.
|
|
68
|
+
|
|
69
|
+
The consequence is a pleasing symmetry: a pattern and a result are mirror images.
|
|
70
|
+
`[a, b] => [b, a]` reads almost pictorially — the same names appear on both sides,
|
|
71
|
+
filled from the input on the left and poured into the output on the right. You never
|
|
72
|
+
write "get element 0, get element 1, now build a new array"; you draw the shape you
|
|
73
|
+
have and the shape you want, and the names connect them. Destructuring stops being an
|
|
74
|
+
operation and becomes a *correspondence*.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Why pattern matching is the only control flow
|
|
79
|
+
|
|
80
|
+
Because functions dispatch by matching their single input against ordered clauses,
|
|
81
|
+
"choosing what to do" and "taking apart the data" are the same act. A clause is
|
|
82
|
+
simultaneously a condition (does this shape match?) and a binding (here are the
|
|
83
|
+
pieces). This collapses two things most languages keep separate — `switch` and
|
|
84
|
+
destructuring assignment — into one.
|
|
85
|
+
|
|
86
|
+
Once control flow *is* matching, recursion naturally absorbs iteration. A list is
|
|
87
|
+
either `[]` or `[head, ...tail]`; those two shapes are the two clauses of almost every
|
|
88
|
+
list function; the recursive call shrinks the input until the base shape matches. The
|
|
89
|
+
"loop" is invisible because it isn't a loop — it's a function rediscovering a smaller
|
|
90
|
+
version of its own input.
|
|
91
|
+
|
|
92
|
+
The one wrinkle this introduces: what should happen when *nothing* matches? Fusion's
|
|
93
|
+
answer is `null` by default. If you want to make a function total, simply append
|
|
94
|
+
`_ => default_value` as a final clause. If you want to make it strict, append
|
|
95
|
+
`_ => !error` instead.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Why a file is exactly one value
|
|
100
|
+
|
|
101
|
+
Earlier drafts had a program be a list of top-level `name = value` bindings plus a
|
|
102
|
+
"main" expression — essentially a second little language stacked on top of the
|
|
103
|
+
expression language, with its own scoping rules (the bindings had to be mutually
|
|
104
|
+
recursive, a `letrec`). The "one value per file" rule deletes all of that. The
|
|
105
|
+
outermost layer of a program is now the same kind of thing as every inner layer: a
|
|
106
|
+
value. A program is just a file whose value happens to be a function, and running it
|
|
107
|
+
is `STDIN | thatFunction`.
|
|
108
|
+
|
|
109
|
+
This also resolved the module system for free. If a file is a value, then referencing
|
|
110
|
+
a file *is* importing a value — no separate `import` construct, no namespace syntax.
|
|
111
|
+
The directory tree becomes the namespace. The standard library is just a folder of
|
|
112
|
+
files. One mechanism (`@`-references) now does top-level structure, modules, and
|
|
113
|
+
library delivery — and, in the current design, built-in access too: `@add` and
|
|
114
|
+
`@Integer` are looked up through the very same `@name` machinery as files. A bare
|
|
115
|
+
`@name` checks for a sibling file, then a built-in, then a standard-library file, so
|
|
116
|
+
your own files can locally shadow a built-in or a stdlib function without affecting
|
|
117
|
+
any other directory. The cost of folding built-ins into `@` is that a bare word like
|
|
118
|
+
`add` is no longer the built-in — it is only ever a pattern hole — so built-ins must
|
|
119
|
+
always be written with `@`.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Why references are lazy
|
|
124
|
+
|
|
125
|
+
If `@`-references resolved eagerly (when a file loads), then a file referencing itself
|
|
126
|
+
would loop forever before it could ever run. Lazy resolution — resolve a reference
|
|
127
|
+
only when it is actually used — is what makes self-recursion and mutual recursion
|
|
128
|
+
possible at all. It is not a performance nicety; it is load-bearing for the whole
|
|
129
|
+
recursion story. Memoization on top means a referenced file is evaluated once and
|
|
130
|
+
shared, so diamond-shaped dependencies are cheap and a referenced-but-unused file is
|
|
131
|
+
never loaded.
|
|
132
|
+
|
|
133
|
+
A side effect is that *data* cycles (files whose values point at each other directly,
|
|
134
|
+
not through a function) are non-productive — there's no pattern match to bottom out
|
|
135
|
+
on. The runtime detects the cycle and yields `!` at that point, while keeping whatever
|
|
136
|
+
productive structure surrounds it.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## The roads not taken (and one we're still tempted by)
|
|
141
|
+
|
|
142
|
+
Two ideas were explored and deliberately set aside, both documented in the design doc.
|
|
143
|
+
|
|
144
|
+
**Operator sugar.** We could write `a + b` and desugar it to `[a, b] | @add`. We rolled
|
|
145
|
+
this back early to keep the core honest, with the explicit intent to reintroduce it
|
|
146
|
+
once the semantics were settled. It is a pure ergonomics layer; it changes nothing
|
|
147
|
+
underneath.
|
|
148
|
+
|
|
149
|
+
**Destructuring functions.** The tantalizing one. Since a function literal is visibly
|
|
150
|
+
a list of `pattern => output` clauses, could we pattern-match *on a function*, taking
|
|
151
|
+
it apart as data? That would give Lisp-like metaprogramming with the same matching
|
|
152
|
+
machinery — and, in its wildest form, the ability to run functions *backwards* (given
|
|
153
|
+
an output, find an input), which is logic programming. We kept it out of the grammar
|
|
154
|
+
because the clean version requires representing patterns as data (a function's pattern
|
|
155
|
+
contains unbound binders, so it isn't an ordinary value), and the wild version would
|
|
156
|
+
turn Fusion from a functional language into a relational one. It remains the most
|
|
157
|
+
interesting possible future for the language, and the most disruptive.
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# How-to guides
|
|
2
|
+
|
|
3
|
+
*These are recipes for specific real-world tasks and assume you already know the
|
|
4
|
+
basics. Scan for the problem you have and copy the solution.*
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Diagnose a program that returns an error unexpectedly
|
|
9
|
+
|
|
10
|
+
When you run a program and see an error payload on stderr, the payload itself
|
|
11
|
+
usually tells you what went wrong (e.g. `"divide: division by zero"` or
|
|
12
|
+
`{"kind":"missing_key","key":"email"}`). For errors arising *during reference
|
|
13
|
+
resolution* — typically a missing file or a parse error in an `@`-referenced file —
|
|
14
|
+
enable extra diagnostics:
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
FUSION_DEBUG=1 fusion program.fsn '...'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
With `FUSION_DEBUG` set, the interpreter prints to stderr the exact path it failed
|
|
21
|
+
to find or the parse error it hit. The most common cause is that the `stdlib/`
|
|
22
|
+
folder is not where the interpreter expects it, so `@`-references can't be resolved.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Emulate control structures from other programming languages
|
|
27
|
+
|
|
28
|
+
1. Branch on a condition (emulate `if`)
|
|
29
|
+
|
|
30
|
+
Compute a boolean, then pipe it into a two-clause function:
|
|
31
|
+
|
|
32
|
+
```fusion
|
|
33
|
+
(n =>
|
|
34
|
+
[n, 0] | @lessThan | (
|
|
35
|
+
true => "negative",
|
|
36
|
+
false => "non-negative"
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
You don't need to restrict yourself to 2 branches and the intermediate values `true` and `false`.
|
|
42
|
+
Here's an elegant way of writing FizzBuzz:
|
|
43
|
+
|
|
44
|
+
```fusion
|
|
45
|
+
(
|
|
46
|
+
n =>
|
|
47
|
+
[
|
|
48
|
+
[n, 3] | @mod,
|
|
49
|
+
[n, 5] | @mod,
|
|
50
|
+
]
|
|
51
|
+
|
|
|
52
|
+
(
|
|
53
|
+
[0, 0] => "FizzBuzz",
|
|
54
|
+
[0, _] => "Fizz",
|
|
55
|
+
[_, 0] => "Buzz",
|
|
56
|
+
_ => n,
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
2. Loop over a list (emulate `for`)
|
|
62
|
+
|
|
63
|
+
Use recursion on lists with the base case `[]` and the recursive case `[x, ...rest]`.
|
|
64
|
+
You could write `sum` like this:
|
|
65
|
+
|
|
66
|
+
```fusion
|
|
67
|
+
(
|
|
68
|
+
[] => 0,
|
|
69
|
+
[x, ...rest] => [x, rest | @] | @add
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
If you don't have a list yet, but want to repeat something `n` times, use the
|
|
74
|
+
standard library function `range` to construct a list. `5 | @range` will evaluate
|
|
75
|
+
to `[0,1,2,3,4]`.
|
|
76
|
+
|
|
77
|
+
If you need further inputs in addition to your list, e.g. a function, use an object:
|
|
78
|
+
`{"function": f, "list": [1, 2, 3]}`.
|
|
79
|
+
|
|
80
|
+
3. Apply a function partially (only provide a subset of required inputs and emulate `currying`)
|
|
81
|
+
|
|
82
|
+
A Fusion function takes exactly one input. Functions that require multiple inputs bundle
|
|
83
|
+
them into an array (or object) and destructure that in the pattern:
|
|
84
|
+
|
|
85
|
+
```fusion
|
|
86
|
+
([a, b] => [a, b] | @add)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Call it as `[3, 4] | @thatFunction`
|
|
90
|
+
|
|
91
|
+
To curry a function call (that means to supply arguments one at a time) write a function that
|
|
92
|
+
takes a single argument (`x` in the example below) and returns a simpler function that only
|
|
93
|
+
needs the remaining arguments (`y` in the example below). Each `=>` consumes one argument
|
|
94
|
+
and hands back a function waiting for the next:
|
|
95
|
+
|
|
96
|
+
```fusion
|
|
97
|
+
(x => (y => [x, y] | @add))
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Call it as `4 | (3 | @thatFunction)`. `3 | f` yields a one-argument function that adds 3,
|
|
101
|
+
and piping a second value into that finishes the sum.
|
|
102
|
+
|
|
103
|
+
With this, you can partially apply functions. Apply the first argument now and then use
|
|
104
|
+
the resulting function later.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Match on a condition involving several captured values
|
|
109
|
+
|
|
110
|
+
A `?` predicate sees **only** the value its own pattern matched — it cannot see
|
|
111
|
+
sibling bindings. To compare two captured values, attach the predicate to the
|
|
112
|
+
**parent container** so it receives the whole thing.
|
|
113
|
+
|
|
114
|
+
For example, to recognise a three-element palindrome you have to compare the first
|
|
115
|
+
and last elements. A predicate guarding `first` on its own could never see `last`,
|
|
116
|
+
so guard the whole array instead:
|
|
117
|
+
|
|
118
|
+
```fusion
|
|
119
|
+
(
|
|
120
|
+
[] => "palindrome",
|
|
121
|
+
[_] => "palindrome",
|
|
122
|
+
[_, ...rest, _] ? ([first, ..., last] => [first, last] | @equals) => rest | @,
|
|
123
|
+
_ => "not a palindrome"
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The outer pattern `[_, ...rest, _]` is what you destructure for retrieving the middle
|
|
128
|
+
of the array which you need to continue your recursion. The inline predicate
|
|
129
|
+
`([first, ..., last] => [first, last] | @equals)` independently destructures the same
|
|
130
|
+
array again to compare its two ends.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Shadow a built-in or stdlib function locally
|
|
135
|
+
|
|
136
|
+
Because a sibling file wins over a built-in or the standard library, you can override
|
|
137
|
+
either — but only for files in the same directory, so you can't break things
|
|
138
|
+
globally. Put an `add.fsn` next to your program and every `@add` *in that directory*
|
|
139
|
+
uses yours; files elsewhere still get the built-in.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Read environment variables
|
|
144
|
+
|
|
145
|
+
`@ENV` evaluates to an object of all environment variables (every value is a string;
|
|
146
|
+
nothing is auto-parsed). Read one with member access:
|
|
147
|
+
|
|
148
|
+
```fusion
|
|
149
|
+
# with CI=1 in the environment, yields the string "1"
|
|
150
|
+
(_ => @ENV.CI)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
A missing variable (`@ENV.NOPE`) yields `!`. Note `@ENV` is itself shadowable: a
|
|
154
|
+
sibling `ENV.fsn` replaces it for that directory.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Load a file by a runtime filename
|
|
159
|
+
|
|
160
|
+
`@map` and friends require the name at parse time and imply the `.fsn` extension.
|
|
161
|
+
When you need to load a file whose name is computed at runtime, or whose name isn't a
|
|
162
|
+
plain identifier (e.g. contains a `.`), use the `@load` built-in. It takes a filename
|
|
163
|
+
**verbatim** — no extension is added — resolved relative to the current file:
|
|
164
|
+
|
|
165
|
+
```fusion
|
|
166
|
+
# Load a file with exactly the given name. Don't append ".fsn".
|
|
167
|
+
("data.config.json" | @load)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```fusion
|
|
171
|
+
# Load a file chosen at runtime.
|
|
172
|
+
(name => name | @load)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
A missing file yields an error.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Keep a recursive file independent of its name and location
|
|
180
|
+
|
|
181
|
+
A recursive function should never call itself by its own filename. Writing `@fact`
|
|
182
|
+
inside `fact.fsn` works until someone renames or moves the file — then the reference
|
|
183
|
+
dangles. Refer to the current file with a bare `@` ("this file", whatever it happens
|
|
184
|
+
to be called) instead:
|
|
185
|
+
|
|
186
|
+
```fusion
|
|
187
|
+
# factorial
|
|
188
|
+
(0 => 1, n ? @Integer => [n, [n, 1] | @subtract | @] | @multiply)
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
If the file evaluates to an **object** and a recursive *helper* lives inside it as a
|
|
192
|
+
member, reach that member with `@.helper` — the current file's value (`@`) followed
|
|
193
|
+
by a `.member` access — rather than `@filename.helper`:
|
|
194
|
+
|
|
195
|
+
```fusion
|
|
196
|
+
{
|
|
197
|
+
"sumTo": (
|
|
198
|
+
0 => 0,
|
|
199
|
+
n ? @Integer => [n, [n, 1] | @subtract | @.sumTo] | @add
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Both forms move and rename along with the file, so nothing dangles when you
|
|
205
|
+
reorganise your directory.
|