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.
- package/fjs/bnf/token_symbol/module.f.d.ts +38 -0
- package/fjs/bnf/token_symbol/module.f.js +48 -0
- package/fjs/bnf/token_symbol/proof.f.d.ts +10 -0
- package/fjs/bnf/token_symbol/proof.f.js +37 -0
- package/fjs/cas/cli/module.f.js +6 -9
- package/fjs/cas/evo/mcp/module.f.d.ts +9 -1
- package/fjs/cas/evo/mcp/module.f.js +52 -14
- package/fjs/cas/evo/mcp/proof.f.d.ts +2 -0
- package/fjs/cas/evo/mcp/proof.f.js +33 -2
- package/fjs/cas/evo/module.f.d.ts +76 -12
- package/fjs/cas/evo/module.f.js +129 -17
- package/fjs/cas/evo/proof.f.d.ts +7 -0
- package/fjs/cas/evo/proof.f.js +119 -0
- package/fjs/cas/mcp/module.f.js +22 -20
- package/fjs/cas/mcp/proof.f.d.ts +1 -1
- package/fjs/cas/mcp/proof.f.js +9 -9
- package/fjs/cas/module.f.js +64 -37
- package/fjs/cas/proof.f.d.ts +3 -9
- package/fjs/cas/proof.f.js +94 -74
- package/fjs/ci/bun/module.f.js +3 -3
- package/fjs/ci/common/module.f.d.ts +1 -2
- package/fjs/ci/common/module.f.js +1 -5
- package/fjs/ci/config/module.f.d.ts +5 -5
- package/fjs/ci/config/module.f.js +5 -5
- package/fjs/ci/deno/module.f.js +3 -3
- package/fjs/ci/module.f.js +3 -4
- package/fjs/ci/node/module.f.js +11 -9
- package/fjs/ci/proof.f.js +2 -0
- package/fjs/cli/module.f.js +2 -2
- package/fjs/common/monoid/module.f.d.ts +46 -2
- package/fjs/common/monoid/module.f.js +44 -0
- package/fjs/common/monoid/proof.f.d.ts +5 -0
- package/fjs/common/monoid/proof.f.js +27 -1
- package/fjs/dev/module.f.js +25 -25
- package/fjs/djs/module.f.js +5 -8
- package/fjs/djs/parser/module.f.d.ts +3 -0
- package/fjs/djs/parser/module.f.js +17 -0
- package/fjs/djs/transpiler/module.f.js +6 -6
- package/fjs/effects/eff/module.f.d.ts +72 -0
- package/fjs/effects/eff/module.f.js +46 -0
- package/fjs/effects/eff/proof.f.d.ts +11 -0
- package/fjs/effects/eff/proof.f.js +58 -0
- package/fjs/effects/list/module.f.d.ts +11 -5
- package/fjs/effects/list/module.f.js +2 -1
- package/fjs/effects/memory/proof.f.js +7 -8
- package/fjs/effects/module.f.d.ts +334 -52
- package/fjs/effects/module.f.js +253 -45
- package/fjs/effects/node/memory/proof.js +3 -2
- package/fjs/effects/node/module.f.d.ts +1 -1
- package/fjs/effects/node/module.f.js +9 -11
- package/fjs/effects/node/proof.f.js +18 -10
- package/fjs/effects/proof.f.d.ts +32 -6
- package/fjs/effects/proof.f.js +131 -39
- package/fjs/emergent_testing/module.f.js +55 -39
- package/fjs/emergent_testing/proof.f.d.ts +4 -0
- package/fjs/emergent_testing/proof.f.js +53 -20
- package/fjs/mcp/module.f.js +8 -8
- package/fjs/mcp/proof.f.js +5 -4
- package/fjs/mcp/stdio/module.f.d.ts +2 -2
- package/fjs/mcp/stdio/module.f.js +12 -12
- package/fjs/media/type/module.f.js +2 -2
- package/fjs/media/type/proof.f.js +7 -10
- package/fjs/module.f.js +4 -8
- package/fjs/types/bigint/module.f.js +4 -3
- package/fjs/types/bit_vec/module.f.js +5 -15
- package/fjs/types/btree/remove/module.f.d.ts +5 -0
- package/fjs/types/btree/remove/module.f.js +12 -0
- package/fjs/types/list/module.f.d.ts +21 -0
- package/fjs/types/list/module.f.js +17 -0
- package/fjs/types/list/proof.f.d.ts +1 -0
- package/fjs/types/list/proof.f.js +24 -1
- package/fjs/types/number/module.f.js +2 -1
- package/fjs/types/string/module.f.js +10 -2
- package/fjs/website/module.f.js +2 -3
- 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)
|
|
52
|
+
: step(write('stdout', v), () => pure(ok(undefined)));
|
|
53
53
|
};
|
|
54
54
|
/**
|
|
55
|
-
* Drives the read-parse-dispatch-write loop for `
|
|
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 = (
|
|
60
|
+
export const stdioTransport = (handler) => step(readLine('stdin'), line => line === null
|
|
61
61
|
? pure(undefined)
|
|
62
|
-
: handleLine(
|
|
63
|
-
const handleLine = (
|
|
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)
|
|
67
|
+
: step(handler(value), resp => resp === null
|
|
68
68
|
? pure(undefined)
|
|
69
|
-
: writeResponse(resp)
|
|
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))
|
|
78
|
-
? writeResponse(internalErrorResponse(null))
|
|
77
|
+
? step(writeResponse(internalErrorResponse(resp.id)), ([t3]) => t3 === 'error'
|
|
78
|
+
? step(writeResponse(internalErrorResponse(null)), () => pure(undefined))
|
|
79
79
|
: pure(undefined))
|
|
80
|
-
: pure(undefined)))
|
|
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) =>
|
|
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 {
|
|
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
|
|
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
|
|
177
|
-
assert(
|
|
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)
|
|
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)
|
|
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 {
|
|
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 =
|
|
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 =
|
|
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 {
|
|
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) {
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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)];
|
package/fjs/website/module.f.js
CHANGED
|
@@ -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.
|
|
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.
|
|
47
|
-
"@types/node": "26.1.
|
|
46
|
+
"@playwright/test": "1.62.0",
|
|
47
|
+
"@types/node": "26.1.2",
|
|
48
48
|
"typescript": "7.0.2"
|
|
49
49
|
}
|
|
50
50
|
}
|