functionalscript 0.38.0 → 0.39.0

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.
Files changed (75) hide show
  1. package/fjs/bnf/token_symbol/module.f.d.ts +38 -0
  2. package/fjs/bnf/token_symbol/module.f.js +48 -0
  3. package/fjs/bnf/token_symbol/proof.f.d.ts +10 -0
  4. package/fjs/bnf/token_symbol/proof.f.js +37 -0
  5. package/fjs/cas/cli/module.f.js +6 -9
  6. package/fjs/cas/evo/mcp/module.f.d.ts +9 -1
  7. package/fjs/cas/evo/mcp/module.f.js +52 -14
  8. package/fjs/cas/evo/mcp/proof.f.d.ts +2 -0
  9. package/fjs/cas/evo/mcp/proof.f.js +33 -2
  10. package/fjs/cas/evo/module.f.d.ts +76 -12
  11. package/fjs/cas/evo/module.f.js +129 -17
  12. package/fjs/cas/evo/proof.f.d.ts +7 -0
  13. package/fjs/cas/evo/proof.f.js +119 -0
  14. package/fjs/cas/mcp/module.f.js +22 -20
  15. package/fjs/cas/mcp/proof.f.d.ts +1 -1
  16. package/fjs/cas/mcp/proof.f.js +9 -9
  17. package/fjs/cas/module.f.js +64 -37
  18. package/fjs/cas/proof.f.d.ts +3 -9
  19. package/fjs/cas/proof.f.js +94 -74
  20. package/fjs/ci/bun/module.f.js +3 -3
  21. package/fjs/ci/common/module.f.d.ts +1 -2
  22. package/fjs/ci/common/module.f.js +1 -5
  23. package/fjs/ci/config/module.f.d.ts +5 -5
  24. package/fjs/ci/config/module.f.js +5 -5
  25. package/fjs/ci/deno/module.f.js +3 -3
  26. package/fjs/ci/module.f.js +3 -4
  27. package/fjs/ci/node/module.f.js +11 -9
  28. package/fjs/ci/proof.f.js +2 -0
  29. package/fjs/cli/module.f.js +2 -2
  30. package/fjs/common/monoid/module.f.d.ts +46 -2
  31. package/fjs/common/monoid/module.f.js +44 -0
  32. package/fjs/common/monoid/proof.f.d.ts +5 -0
  33. package/fjs/common/monoid/proof.f.js +27 -1
  34. package/fjs/dev/module.f.js +25 -25
  35. package/fjs/djs/module.f.js +5 -8
  36. package/fjs/djs/parser/module.f.d.ts +3 -0
  37. package/fjs/djs/parser/module.f.js +17 -0
  38. package/fjs/djs/transpiler/module.f.js +6 -6
  39. package/fjs/effects/eff/module.f.d.ts +72 -0
  40. package/fjs/effects/eff/module.f.js +46 -0
  41. package/fjs/effects/eff/proof.f.d.ts +11 -0
  42. package/fjs/effects/eff/proof.f.js +58 -0
  43. package/fjs/effects/list/module.f.d.ts +11 -5
  44. package/fjs/effects/list/module.f.js +2 -1
  45. package/fjs/effects/memory/proof.f.js +7 -8
  46. package/fjs/effects/module.f.d.ts +334 -52
  47. package/fjs/effects/module.f.js +253 -45
  48. package/fjs/effects/node/memory/proof.js +3 -2
  49. package/fjs/effects/node/module.f.d.ts +1 -1
  50. package/fjs/effects/node/module.f.js +9 -11
  51. package/fjs/effects/node/proof.f.js +18 -10
  52. package/fjs/effects/proof.f.d.ts +32 -6
  53. package/fjs/effects/proof.f.js +131 -39
  54. package/fjs/emergent_testing/module.f.js +55 -39
  55. package/fjs/emergent_testing/proof.f.d.ts +4 -0
  56. package/fjs/emergent_testing/proof.f.js +53 -20
  57. package/fjs/mcp/module.f.js +8 -8
  58. package/fjs/mcp/proof.f.js +5 -4
  59. package/fjs/mcp/stdio/module.f.d.ts +2 -2
  60. package/fjs/mcp/stdio/module.f.js +12 -12
  61. package/fjs/media/type/module.f.js +2 -2
  62. package/fjs/media/type/proof.f.js +7 -10
  63. package/fjs/module.f.js +4 -8
  64. package/fjs/types/bigint/module.f.js +4 -3
  65. package/fjs/types/bit_vec/module.f.js +5 -15
  66. package/fjs/types/btree/remove/module.f.d.ts +5 -0
  67. package/fjs/types/btree/remove/module.f.js +12 -0
  68. package/fjs/types/list/module.f.d.ts +21 -0
  69. package/fjs/types/list/module.f.js +17 -0
  70. package/fjs/types/list/proof.f.d.ts +1 -0
  71. package/fjs/types/list/proof.f.js +24 -1
  72. package/fjs/types/number/module.f.js +2 -1
  73. package/fjs/types/string/module.f.js +10 -2
  74. package/fjs/website/module.f.js +2 -3
  75. package/package.json +3 -3
@@ -29,7 +29,7 @@
29
29
  *
30
30
  * @module
31
31
  */
32
- import { pure } from '../../effects/module.f.js';
32
+ import { pure, step } from '../../effects/module.f.js';
33
33
  import { readLine, write } from '../../effects/node/module.f.js';
34
34
  import { tryUtf8 } from '../../text/module.f.js';
35
35
  import { stringToList } from '../../text/utf16/module.f.js';
@@ -49,24 +49,24 @@ const writeResponse = (resp) => {
49
49
  const v = tryUtf8(stringifyJson(resp) + '\n');
50
50
  return v === null
51
51
  ? pure(error(undefined))
52
- : write('stdout', v).step(() => pure(ok(undefined)));
52
+ : step(write('stdout', v), () => pure(ok(undefined)));
53
53
  };
54
54
  /**
55
- * Drives the read-parse-dispatch-write loop for `step` over stdin/stdout.
55
+ * Drives the read-parse-dispatch-write loop for `handler` over stdin/stdout.
56
56
  *
57
57
  * Recurses after each handled line; terminates (resolving to `void`) when
58
58
  * `readLine` reports EOF.
59
59
  */
60
- export const stdioTransport = (step) => readLine('stdin').step(line => line === null
60
+ export const stdioTransport = (handler) => step(readLine('stdin'), line => line === null
61
61
  ? pure(undefined)
62
- : handleLine(step)(line));
63
- const handleLine = (step) => (line) => {
62
+ : handleLine(handler)(line));
63
+ const handleLine = (handler) => (line) => {
64
64
  const [t, value] = parse(tokenize(stringToList(line)));
65
- return (t === 'error'
65
+ return step(t === 'error'
66
66
  ? writeResponse(parseErrorResponse)
67
- : step(value).step(resp => resp === null
67
+ : step(handler(value), resp => resp === null
68
68
  ? pure(undefined)
69
- : writeResponse(resp).step(([t2]) => t2 === 'error'
69
+ : step(writeResponse(resp), ([t2]) => t2 === 'error'
70
70
  // The real response didn't fit. Retry with a fixed, small
71
71
  // internal-error body carrying `resp.id` — but a
72
72
  // caller-controlled `id` (e.g. a very large string) can
@@ -74,8 +74,8 @@ const handleLine = (step) => (line) => {
74
74
  // that retry is bounded by one more: an `id: null`
75
75
  // internal-error, whose fully-constant shape is the only
76
76
  // line in this transport guaranteed to always encode.
77
- ? writeResponse(internalErrorResponse(resp.id)).step(([t3]) => t3 === 'error'
78
- ? writeResponse(internalErrorResponse(null)).step(() => pure(undefined))
77
+ ? step(writeResponse(internalErrorResponse(resp.id)), ([t3]) => t3 === 'error'
78
+ ? step(writeResponse(internalErrorResponse(null)), () => pure(undefined))
79
79
  : pure(undefined))
80
- : pure(undefined)))).step(() => stdioTransport(step));
80
+ : pure(undefined))), () => stdioTransport(handler));
81
81
  };
@@ -34,7 +34,7 @@
34
34
  */
35
35
  import { msb, fromSentinel, length, u8List } from '../../types/bit_vec/module.f.js';
36
36
  import { iterable } from '../../types/list/module.f.js';
37
- import { pure } from '../../effects/module.f.js';
37
+ import { pure, step } from '../../effects/module.f.js';
38
38
  import { ok, error } from '../../types/result/module.f.js';
39
39
  import { isValidCodePoint, isTextCodePoint } from '../../text/code_point/module.f.js';
40
40
  import { utf8ByteToCodePointOp } from '../../text/utf8/module.f.js';
@@ -216,7 +216,7 @@ export const detectVec = (bytes) => finish(push(detectInit)(bytes));
216
216
  * item short-circuits into the `IoResult` error.
217
217
  */
218
218
  export const detectStream = (stream) => {
219
- const loop = (s) => (l) => l.step((node) => {
219
+ const loop = (s) => (l) => step(l, (node) => {
220
220
  if (node === undefined) {
221
221
  return pure(ok(finish(s)));
222
222
  }
@@ -1,6 +1,6 @@
1
1
  import { assert, assertEq } from '../../asserts/module.f.js';
2
2
  import { msb, u8ListToVec, vec8, repeat, empty } from '../../types/bit_vec/module.f.js';
3
- import { decode } from '../../effects/module.f.js';
3
+ import { runPure } from '../../effects/module.f.js';
4
4
  import { nonEmpty, empty as emptyList } from '../../effects/list/module.f.js';
5
5
  import { ok } from '../../types/result/module.f.js';
6
6
  import { detect, detectStream, detectVec } from './module.f.js';
@@ -8,17 +8,13 @@ import { detect, detectStream, detectVec } from './module.f.js';
8
8
  // store would hold the leading bytes of a stored blob.
9
9
  const bytes = (...b) => u8ListToVec(msb)(b);
10
10
  // ── Streaming detector helpers ──────────────────────────────────────────────────
11
- // Evaluates a fully pure effect (no operations) to its result.
12
- const runPure = (e) => {
13
- const d = decode(e);
14
- assert(d.done, 'effect is not pure');
15
- return d.result;
16
- };
17
11
  // Builds a CAS-style read stream from a sequence of ok(chunk) items.
18
12
  const stream = (...chunks) => chunks.reduceRight((tail, c) => nonEmpty(ok(c), tail), emptyList());
19
13
  // Runs the streaming detector over the given chunks and unwraps the metadata.
20
14
  const detectChunks = (...chunks) => {
21
- const r = runPure(detectStream(stream(...chunks)));
15
+ const o = runPure(detectStream(stream(...chunks)));
16
+ assert(o.length === 1, 'effect is not pure');
17
+ const [r] = o;
22
18
  assert(r[0] !== 'error', r[1]);
23
19
  return r[1];
24
20
  };
@@ -173,8 +169,9 @@ export const proof = {
173
169
  // A read `error` item short-circuits into the IoResult error.
174
170
  readErrorSurfaces: () => {
175
171
  const errStream = nonEmpty(['error', 'boom'], emptyList());
176
- const r = runPure(detectStream(errStream));
177
- assert(r[0] === 'error');
172
+ const o = runPure(detectStream(errStream));
173
+ assert(o.length === 1, 'effect is not pure');
174
+ assert(o[0][0] === 'error');
178
175
  },
179
176
  // Empty stream: zero-length text/plain.
180
177
  empty: () => {
package/fjs/module.f.js CHANGED
@@ -10,7 +10,8 @@ import { main as ciMain } from './ci/module.f.js';
10
10
  import { import_ } from './effects/node/module.f.js';
11
11
  import { dispatch } from './cli/module.f.js';
12
12
  import { casMcpServer } from './cas/mcp/module.f.js';
13
- import { pure } from './effects/module.f.js';
13
+ import { pure, step } from './effects/module.f.js';
14
+ import { unwrap } from './types/result/module.f.js';
14
15
  const commands = [
15
16
  {
16
17
  names: ['test', 't'],
@@ -30,7 +31,7 @@ const commands = [
30
31
  {
31
32
  names: ['mcp', 'm'],
32
33
  description: 'Run an MCP server over stdio exposing the CAS and Evo (subjects/heads) as tools',
33
- handler: ({ home }) => casMcpServer(home).step(() => pure(0)),
34
+ handler: ({ home }) => step(casMcpServer(home), () => pure(0)),
34
35
  },
35
36
  {
36
37
  names: ['ci', 'i'],
@@ -42,12 +43,7 @@ const commands = [
42
43
  description: 'Run a FunctionalScript module as a NodeProgram',
43
44
  handler: options => {
44
45
  const [file, ...args] = options.args;
45
- return import_(file).step(([s, v]) => {
46
- if (s === 'error') {
47
- throw v;
48
- }
49
- return v.main({ ...options, args });
50
- });
46
+ return step(import_(file), x => unwrap(x).main({ ...options, args }));
51
47
  },
52
48
  },
53
49
  ];
@@ -17,7 +17,8 @@
17
17
  * ```
18
18
  */
19
19
  import { cmp } from '../function/compare/module.f.js';
20
- import { reduce } from '../list/module.f.js';
20
+ import {} from '../list/module.f.js';
21
+ import { fold } from '../../common/monoid/module.f.js';
21
22
  /**
22
23
  * Adds two `bigint` values.
23
24
  *
@@ -31,7 +32,7 @@ export const addition = a => b => a + b;
31
32
  * @param input - A list of bigint values.
32
33
  * @returns The sum of all values in the list.
33
34
  */
34
- export const sum = reduce(addition)(0n);
35
+ export const sum = fold({ identity: 0n, operation: addition });
35
36
  /**
36
37
  * Multiplies two `bigint` values.
37
38
  *
@@ -45,7 +46,7 @@ export const multiple = a => b => a * b;
45
46
  * @param input - A list of bigint values.
46
47
  * @returns The product of all values in the list.
47
48
  */
48
- export const product = reduce(multiple)(1n);
49
+ export const product = fold({ identity: 1n, operation: multiple });
49
50
  /**
50
51
  * Calculates the absolute value of a `bigint`.
51
52
  *
@@ -23,7 +23,7 @@
23
23
  */
24
24
  import { bitLength, divUp, mask, maxLength, xor } from '../bigint/module.f.js';
25
25
  import { flip, identity } from '../function/module.f.js';
26
- import { iterable, map } from '../list/module.f.js';
26
+ import { map, tryFold } from '../list/module.f.js';
27
27
  import { asBase, asNominal } from '../nominal/module.f.js';
28
28
  import { repeat as mRepeat } from '../../common/monoid/module.f.js';
29
29
  import { cmp, max, min } from '../function/compare/module.f.js';
@@ -174,24 +174,14 @@ const listToVecOp = (unpackConcat) => ({
174
174
  * the final reduce prepends higher (earlier) slots in front of accumulated
175
175
  * later runs. An empty list yields `unpackEmpty`.
176
176
  *
177
+ * Returns `null` as soon as the accumulated length exceeds `maxLength`, which
178
+ * `tryFold` propagates by abandoning the rest of the list.
179
+ *
177
180
  * This is the bit-vector analogue of a builder that accumulates appended pieces
178
181
  * and materializes the combined result on demand, such as `StringBuilder`
179
182
  * (Java, C#) or `strings.Builder` (Go).
180
183
  */
181
- const unpackListToVec = (unpackConcat) => {
182
- const { init, update, end } = listToVecOp(unpackConcat);
183
- return (list) => {
184
- let result = init;
185
- for (const e of iterable(list)) {
186
- const candidate = update(e, result);
187
- if (candidate === null) {
188
- return null;
189
- }
190
- result = candidate;
191
- }
192
- return end(result);
193
- };
194
- };
184
+ const unpackListToVec = (unpackConcat) => tryFold(listToVecOp(unpackConcat));
195
185
  const bo = ({ front, removeFront, norm, uintCmp, unpackSplit, unpackConcatUint }) => {
196
186
  const unpackPopFront = (len) => {
197
187
  const m = mask(len);
@@ -7,3 +7,8 @@ import { type TNode, type Tree } from '../types/module.f.ts';
7
7
  import type { Compare } from '../../function/compare/module.f.ts';
8
8
  export declare const nodeRemove: <T>(c: Compare<T>) => (node: TNode<T>) => Tree<T>;
9
9
  export declare const remove: <T>(c: Compare<T>) => (tree: Tree<T>) => Tree<T>;
10
+ export declare const proof: {
11
+ throw: {
12
+ reduceValue0DefaultBranch: () => void;
13
+ };
14
+ };
@@ -172,3 +172,15 @@ export const nodeRemove = (c) => (node) => {
172
172
  return collapseRoot(reduce(initReduce(tf)(first))(tt));
173
173
  };
174
174
  export const remove = c => map(nodeRemove(c));
175
+ // `reduceValue0`/`reduceValue2`/`initValue0`/`initValue1` merge a Branch1's lone
176
+ // child into its Branch3 sibling. The sibling is always a Branch (length 3 or 5)
177
+ // in every reachable tree shape, so the `default` arm guarding a mismatched
178
+ // (leaf) sibling length can't be hit through the public `remove` API. Exercise
179
+ // it directly here so the invariant guard itself stays covered.
180
+ export const proof = {
181
+ throw: {
182
+ reduceValue0DefaultBranch: () => {
183
+ reduceValue0([['leaf']])([['x'], 's', ['y']]);
184
+ },
185
+ },
186
+ };
@@ -1,3 +1,4 @@
1
+ import type { Nullable } from '../nullable/module.f.ts';
1
2
  import { type Scan, type StateScan, type Fold, type Reduce, type Equal } from '../function/operator/module.f.ts';
2
3
  export type List<T> = NotLazy<T> | Thunk<T>;
3
4
  type NotLazy<T> = Result<T> | Concat<T> | readonly T[];
@@ -44,6 +45,26 @@ export declare const stateScan: <I, S, O>(op: StateScan<I, S, O>) => (init: S) =
44
45
  export declare const foldScan: <I, O>(op: Fold<I, O>) => (init: O) => (input: List<I>) => Thunk<O>;
45
46
  export declare const fold: <I, O>(op: Fold<I, O>) => (init: O) => (input: List<I>) => O;
46
47
  export declare const reduce: <T>(op: Reduce<T>) => <D>(def: D) => (input: List<T>) => D | T;
48
+ /**
49
+ * A fold that can bail out early, packaged as plain data.
50
+ *
51
+ * `init` is the starting state, `update` advances the state by one item and
52
+ * returns `null` to abort the whole fold, and `end` finalizes the surviving
53
+ * state into a result. Keeping the three parts together lets `tryFold` drive
54
+ * any short-circuiting accumulation without knowing its domain.
55
+ */
56
+ export type Accumulator<I, T, R> = {
57
+ readonly init: T;
58
+ readonly update: (i: I, state: T) => Nullable<T>;
59
+ readonly end: (state: T) => R;
60
+ };
61
+ /**
62
+ * Folds `input` with an {@link Accumulator}, short-circuiting to `null` the
63
+ * moment `update` rejects an item; otherwise finalizes with `end`. This is the
64
+ * list-level `try*` sibling of {@link fold} for accumulations that can fail
65
+ * partway through (see the `try*`/`Nullable` convention in `AGENTS.md`).
66
+ */
67
+ export declare const tryFold: <I, T, R>({ init, update, end }: Accumulator<I, T, R>) => (input: List<I>) => Nullable<R>;
47
68
  export declare const length: <T>(input: List<T>) => number;
48
69
  export type Entry<T> = readonly [number, T];
49
70
  export declare const entries: <T>(input: List<T>) => Thunk<Entry<T>>;
@@ -127,6 +127,23 @@ export const stateScan = op => compose(stateScanToScan(op))(scan);
127
127
  export const foldScan = op => compose(foldToScan(op))(scan);
128
128
  export const fold = op => init => compose(foldScan(op)(init))(last(init));
129
129
  export const reduce = op => def => compose(scan(reduceToScan(op)))(last(def));
130
+ /**
131
+ * Folds `input` with an {@link Accumulator}, short-circuiting to `null` the
132
+ * moment `update` rejects an item; otherwise finalizes with `end`. This is the
133
+ * list-level `try*` sibling of {@link fold} for accumulations that can fail
134
+ * partway through (see the `try*`/`Nullable` convention in `AGENTS.md`).
135
+ */
136
+ export const tryFold = ({ init, update, end }) => (input) => {
137
+ let state = init;
138
+ for (const i of iterable(input)) {
139
+ const candidate = update(i, state);
140
+ if (candidate === null) {
141
+ return null;
142
+ }
143
+ state = candidate;
144
+ }
145
+ return end(state);
146
+ };
130
147
  const lengthList = list => () => {
131
148
  const notLazy = trampoline(list);
132
149
  if (notLazy === null) {
@@ -19,6 +19,7 @@ export declare const proof: {
19
19
  drop: (() => void)[];
20
20
  additionTests: (() => void)[];
21
21
  entries: (() => void)[];
22
+ tryFold: readonly [() => void, () => void, () => void];
22
23
  reverse: (() => void)[];
23
24
  zip: (() => void)[];
24
25
  logic: typeof logic;
@@ -1,4 +1,4 @@
1
- import { length, concat, countdown, cycle, drop, dropWhile, entries, every, filter, find, flat, flatMap, map, next, reduce, reverse, scan, some, take, takeWhile, toArray, zip, first, filterMap, isEmpty, equal } from './module.f.js';
1
+ import { length, concat, countdown, cycle, drop, dropWhile, entries, every, filter, find, flat, flatMap, map, next, reduce, reverse, scan, some, take, takeWhile, toArray, zip, first, filterMap, isEmpty, equal, tryFold } from './module.f.js';
2
2
  import { stringify } from '../../media/json/module.f.js';
3
3
  import { sort } from '../object/module.f.js';
4
4
  import { addition, strictEqual, reduceToScan } from '../function/operator/module.f.js';
@@ -112,6 +112,28 @@ const entriesTest = [
112
112
  assertEq(result, '[[0,"hello"],[1,"world"]]');
113
113
  }
114
114
  ];
115
+ const sumUpTo = (limit) => ({
116
+ init: 0,
117
+ update: (i, state) => {
118
+ const next = state + i;
119
+ return next > limit ? null : next;
120
+ },
121
+ end: (state) => state * 10,
122
+ });
123
+ const tryFoldTest = [
124
+ () => {
125
+ const result = tryFold(sumUpTo(100))([1, 2, 3]);
126
+ assertEq(result, 60);
127
+ },
128
+ () => {
129
+ const result = tryFold(sumUpTo(4))([1, 2, 3]);
130
+ assertEq(result, null);
131
+ },
132
+ () => {
133
+ const result = tryFold(sumUpTo(100))([]);
134
+ assertEq(result, 0);
135
+ },
136
+ ];
115
137
  const reverseTest = [
116
138
  () => {
117
139
  const result = str(reverse([]));
@@ -257,6 +279,7 @@ export const proof = {
257
279
  drop: dropTest,
258
280
  additionTests,
259
281
  entries: entriesTest,
282
+ tryFold: tryFoldTest,
260
283
  reverse: reverseTest,
261
284
  zip: zipTest,
262
285
  logic,
@@ -7,7 +7,8 @@
7
7
  import { reduce } from '../list/module.f.js';
8
8
  import { addition } from '../function/operator/module.f.js';
9
9
  import { cmp as uCmp, min as uMin, max as uMax } from '../function/compare/module.f.js';
10
- export const sum = reduce(addition)(0);
10
+ import { fold } from '../../common/monoid/module.f.js';
11
+ export const sum = fold({ identity: 0, operation: addition });
11
12
  export const min = reduce(uMin)(null);
12
13
  export const max = reduce(uMax)(null);
13
14
  export const cmp = uCmp;
@@ -18,10 +18,18 @@
18
18
  import { reduce as listReduce, repeat as listRepeat } from '../list/module.f.js';
19
19
  import { compose } from '../function/module.f.js';
20
20
  import { cmp as uCmp } from '../function/compare/module.f.js';
21
- import { join as joinOp, concat as concatOp } from '../function/operator/module.f.js';
21
+ import { join as joinOp } from '../function/operator/module.f.js';
22
+ import { fold } from '../../common/monoid/module.f.js';
23
+ // `join`'s per-separator reduction is seeded at `''` but is *not* a monoid fold:
24
+ // `joinOp(sep)` has no identity (`joinOp(sep)('')(x)` prepends a separator), so
25
+ // it stays a hand-seeded `reduce` rather than going through `monoid.fold`.
22
26
  const reduce = o => listReduce(o)('');
23
27
  export const join = compose(joinOp)(reduce);
24
- export const concat = reduce(concatOp);
28
+ // String concatenation with identity `''` is a lawful monoid, so `concat` is a
29
+ // monoid fold. The operation is accumulator-first (`a` on the left), matching
30
+ // `monoid.fold`'s convention — unlike the element-first `concat` in
31
+ // `function/operator`, which is written for `list.reduce`.
32
+ export const concat = fold({ identity: '', operation: a => b => a + b });
25
33
  export const repeat = v => compose(listRepeat(v))(concat);
26
34
  export const cmp = uCmp;
27
35
  export const splitAt = (p) => (v) => [v.substring(0, p), v.substring(p)];
@@ -5,11 +5,10 @@
5
5
  */
6
6
  import { htmlUtf8 } from '../media/html/module.f.js';
7
7
  import { writeFile } from '../effects/node/module.f.js';
8
- import { pure } from '../effects/module.f.js';
8
+ import { pure, step } from '../effects/module.f.js';
9
9
  const html = htmlUtf8()(['a',
10
10
  { href: 'https://github.com/functionalscript/functionalscript' },
11
11
  'GitHub Repository'
12
12
  ]);
13
- const program = writeFile('index.html', html)
14
- .step(() => pure(0));
13
+ const program = step(writeFile('index.html', html), () => pure(0));
15
14
  export const main = () => program;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "functionalscript",
3
- "version": "0.38.0",
3
+ "version": "0.39.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "**/*.js",
@@ -43,8 +43,8 @@
43
43
  },
44
44
  "homepage": "https://github.com/functionalscript/functionalscript#readme",
45
45
  "devDependencies": {
46
- "@playwright/test": "1.61.1",
47
- "@types/node": "26.1.1",
46
+ "@playwright/test": "1.62.0",
47
+ "@types/node": "26.1.2",
48
48
  "typescript": "7.0.2"
49
49
  }
50
50
  }