initial commit
This commit is contained in:
42
benchmarks/query/async.bench.ts
Normal file
42
benchmarks/query/async.bench.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { EXTENDED } from '../helpers/bench-options.js';
|
||||
import {
|
||||
okSome,
|
||||
okNone,
|
||||
QueryPromise,
|
||||
} from '../helpers/imports.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// QueryPromise.from — returns value
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('QueryPromise.from — returns value', () => {
|
||||
bench('native-monad', async () => {
|
||||
await QueryPromise.from(() => Promise.resolve(42));
|
||||
}, EXTENDED);
|
||||
// No other library has a three-state async wrapper
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// QueryPromise.from — returns null
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('QueryPromise.from — returns null', () => {
|
||||
bench('native-monad', async () => {
|
||||
await QueryPromise.from(() => Promise.resolve(null));
|
||||
}, EXTENDED);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// QueryPromise.all — all OkSome
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
for (const size of [10, 100] as const) {
|
||||
describe(`QueryPromise.all — ${size} items, all OkSome`, () => {
|
||||
const promises = () => Array.from({ length: size }, (_, i) => Promise.resolve(okSome(i)));
|
||||
|
||||
bench('native-monad', async () => {
|
||||
await QueryPromise.all(promises());
|
||||
}, EXTENDED);
|
||||
});
|
||||
}
|
||||
128
benchmarks/query/chaining.bench.ts
Normal file
128
benchmarks/query/chaining.bench.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { STANDARD } from '../helpers/bench-options.js';
|
||||
import {
|
||||
okSome,
|
||||
okNone,
|
||||
errNone,
|
||||
E,
|
||||
O,
|
||||
pipe,
|
||||
} from '../helpers/imports.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 5-step pipeline — OkSome path
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query chaining — 5-step pipeline (all OkSome)', () => {
|
||||
bench('native-monad', () => {
|
||||
okSome(42)
|
||||
.map(x => x * 2)
|
||||
.flatMap(x => (x > 0 ? okSome(x) : okNone()))
|
||||
.map(x => ({ value: x }))
|
||||
.flatMap(x => okSome({ ...x, label: 'result' }))
|
||||
.map(x => JSON.stringify(x));
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts (nested)', () => {
|
||||
pipe(
|
||||
E.right(O.some(42)) as E.Either<string, O.Option<number>>,
|
||||
E.map(O.map(x => x * 2)),
|
||||
E.flatMap(inner =>
|
||||
pipe(
|
||||
inner,
|
||||
O.match(
|
||||
() => E.right(O.none) as E.Either<string, O.Option<number>>,
|
||||
v => (v > 0 ? E.right(O.some(v)) : E.right(O.none)) as E.Either<string, O.Option<number>>,
|
||||
),
|
||||
),
|
||||
),
|
||||
E.map(O.map(x => ({ value: x }))),
|
||||
E.flatMap(inner =>
|
||||
pipe(
|
||||
inner,
|
||||
O.match(
|
||||
() => E.right(O.none) as E.Either<string, O.Option<{ value: number; label: string }>>,
|
||||
v => E.right(O.some({ ...v, label: 'result' })),
|
||||
),
|
||||
),
|
||||
),
|
||||
E.map(O.map(x => JSON.stringify(x))),
|
||||
);
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ErrNone short-circuit — 9 no-op maps
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query chaining — ErrNone at step 1, 9 no-op maps', () => {
|
||||
const nm = errNone('fail');
|
||||
const fp = E.left('fail') as E.Either<string, O.Option<number>>;
|
||||
const fn = (x: number) => x + 1;
|
||||
|
||||
bench('native-monad', () => {
|
||||
nm.map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn);
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts (nested)', () => {
|
||||
pipe(
|
||||
fp,
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
);
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OkNone short-circuit — 9 no-op maps
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query chaining — OkNone, 9 no-op maps', () => {
|
||||
const nm = okNone();
|
||||
const fp = E.right(O.none) as E.Either<string, O.Option<number>>;
|
||||
const fn = (x: number) => x + 1;
|
||||
|
||||
bench('native-monad', () => {
|
||||
nm.map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn).map(fn);
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts (nested)', () => {
|
||||
pipe(
|
||||
fp,
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
E.map(O.map(fn)),
|
||||
);
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 20-step deep map chain — OkSome path
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query chaining — 20 maps on OkSome', () => {
|
||||
const fn = (x: number) => x + 1;
|
||||
|
||||
bench('native-monad', () => {
|
||||
let r = okSome(0);
|
||||
for (let i = 0; i < 20; i++) r = r.map(fn);
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts (nested)', () => {
|
||||
let r = E.right(O.some(0)) as E.Either<string, O.Option<number>>;
|
||||
for (let i = 0; i < 20; i++) r = pipe(r, E.map(O.map(fn)));
|
||||
}, STANDARD);
|
||||
});
|
||||
144
benchmarks/query/collection.bench.ts
Normal file
144
benchmarks/query/collection.bench.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { STANDARD, EXTENDED } from '../helpers/bench-options.js';
|
||||
import { SIZES, errorIndices } from '../helpers/data-generators.js';
|
||||
import {
|
||||
okSome,
|
||||
okNone,
|
||||
errNone,
|
||||
Query,
|
||||
E,
|
||||
O,
|
||||
pipe,
|
||||
} from '../helpers/imports.js';
|
||||
import * as RA from 'fp-ts/ReadonlyArray';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Query.all — all OkSome
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
for (const size of SIZES) {
|
||||
const opts = size >= 10_000 ? EXTENDED : STANDARD;
|
||||
|
||||
const nmAll = Array.from({ length: size }, (_, i) => okSome(i));
|
||||
const fpAll = Array.from({ length: size }, (_, i) =>
|
||||
E.right(O.some(i)) as E.Either<string, O.Option<number>>,
|
||||
);
|
||||
|
||||
describe(`Query.all — ${size} items, all OkSome`, () => {
|
||||
bench('native-monad', () => { Query.all(nmAll); }, opts);
|
||||
bench('fp-ts (nested, manual)', () => {
|
||||
const values: number[] = [];
|
||||
for (const item of fpAll) {
|
||||
if (E.isLeft(item)) return item;
|
||||
if (O.isNone(item.right)) continue;
|
||||
values.push(item.right.value);
|
||||
}
|
||||
return E.right(O.some(values));
|
||||
}, opts);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Query.all — mixed (OkSome + OkNone + ErrNone)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
for (const size of SIZES) {
|
||||
const opts = size >= 10_000 ? EXTENDED : STANDARD;
|
||||
const errIdx = errorIndices(size, 0.1);
|
||||
const noneIdx = errorIndices(size, 0.15);
|
||||
|
||||
const nmMixed = Array.from({ length: size }, (_, i) => {
|
||||
if (errIdx.has(i)) return errNone('fail');
|
||||
if (noneIdx.has(i)) return okNone();
|
||||
return okSome(i);
|
||||
});
|
||||
|
||||
const fpMixed = Array.from({ length: size }, (_, i) => {
|
||||
if (errIdx.has(i)) return E.left('fail') as E.Either<string, O.Option<number>>;
|
||||
if (noneIdx.has(i)) return E.right(O.none) as E.Either<string, O.Option<number>>;
|
||||
return E.right(O.some(i)) as E.Either<string, O.Option<number>>;
|
||||
});
|
||||
|
||||
describe(`Query.all — ${size} items, mixed (short-circuit)`, () => {
|
||||
bench('native-monad', () => { Query.all(nmMixed); }, opts);
|
||||
bench('fp-ts (nested, manual)', () => {
|
||||
const values: number[] = [];
|
||||
for (const item of fpMixed) {
|
||||
if (E.isLeft(item)) return item;
|
||||
if (O.isNone(item.right)) continue;
|
||||
values.push(item.right.value);
|
||||
}
|
||||
return E.right(O.some(values));
|
||||
}, opts);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Query.partition
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
for (const size of SIZES) {
|
||||
const opts = size >= 10_000 ? EXTENDED : STANDARD;
|
||||
const errIdx = errorIndices(size, 0.1);
|
||||
const noneIdx = errorIndices(size, 0.15);
|
||||
|
||||
const nmMixed = Array.from({ length: size }, (_, i) => {
|
||||
if (errIdx.has(i)) return errNone('fail');
|
||||
if (noneIdx.has(i)) return okNone();
|
||||
return okSome(i);
|
||||
});
|
||||
|
||||
const fpMixed = Array.from({ length: size }, (_, i) => {
|
||||
if (errIdx.has(i)) return E.left('fail') as E.Either<string, O.Option<number>>;
|
||||
if (noneIdx.has(i)) return E.right(O.none) as E.Either<string, O.Option<number>>;
|
||||
return E.right(O.some(i)) as E.Either<string, O.Option<number>>;
|
||||
});
|
||||
|
||||
describe(`Query.partition — ${size} items`, () => {
|
||||
bench('native-monad', () => { Query.partition(nmMixed); }, opts);
|
||||
bench('fp-ts (nested, manual)', () => {
|
||||
const values: number[] = [];
|
||||
const errors: string[] = [];
|
||||
let noneCount = 0;
|
||||
for (const item of fpMixed) {
|
||||
if (E.isLeft(item)) { errors.push(item.left); continue; }
|
||||
if (O.isNone(item.right)) { noneCount++; continue; }
|
||||
values.push(item.right.value);
|
||||
}
|
||||
return { values, noneCount, errors };
|
||||
}, opts);
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Query.compact
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
for (const size of SIZES) {
|
||||
const opts = size >= 10_000 ? EXTENDED : STANDARD;
|
||||
const errIdx = errorIndices(size, 0.1);
|
||||
const noneIdx = errorIndices(size, 0.15);
|
||||
|
||||
const nmMixed = Array.from({ length: size }, (_, i) => {
|
||||
if (errIdx.has(i)) return errNone('fail');
|
||||
if (noneIdx.has(i)) return okNone();
|
||||
return okSome(i);
|
||||
});
|
||||
|
||||
const fpMixed = Array.from({ length: size }, (_, i) => {
|
||||
if (errIdx.has(i)) return E.left('fail') as E.Either<string, O.Option<number>>;
|
||||
if (noneIdx.has(i)) return E.right(O.none) as E.Either<string, O.Option<number>>;
|
||||
return E.right(O.some(i)) as E.Either<string, O.Option<number>>;
|
||||
});
|
||||
|
||||
describe(`Query.compact — ${size} items`, () => {
|
||||
bench('native-monad', () => { Query.compact(nmMixed); }, opts);
|
||||
bench('fp-ts (nested, manual)', () => {
|
||||
const values: number[] = [];
|
||||
for (const item of fpMixed) {
|
||||
if (E.isRight(item) && O.isSome(item.right)) values.push(item.right.value);
|
||||
}
|
||||
return values;
|
||||
}, opts);
|
||||
});
|
||||
}
|
||||
65
benchmarks/query/construction.bench.ts
Normal file
65
benchmarks/query/construction.bench.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { STANDARD } from '../helpers/bench-options.js';
|
||||
import {
|
||||
okSome,
|
||||
okNone,
|
||||
errNone,
|
||||
TsOk,
|
||||
TsErr,
|
||||
TsSome,
|
||||
TsNone,
|
||||
OxOk,
|
||||
OxErr,
|
||||
OxSome,
|
||||
OxNone,
|
||||
E,
|
||||
O,
|
||||
TmResult,
|
||||
TmMaybe,
|
||||
} from '../helpers/imports.js';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OkSome — vs nested Result<Option<T>, E>
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query construction — OkSome (primitive)', () => {
|
||||
bench('native-monad', () => { okSome(42); }, STANDARD);
|
||||
bench('ts-results-es (nested)', () => { TsOk(TsSome(42)); }, STANDARD);
|
||||
bench('oxide.ts (nested)', () => { OxOk(OxSome(42)); }, STANDARD);
|
||||
bench('fp-ts (nested)', () => { E.right(O.some(42)); }, STANDARD);
|
||||
bench('true-myth (nested)', () => { TmResult.ok(TmMaybe.just(42)); }, STANDARD);
|
||||
});
|
||||
|
||||
describe('Query construction — OkSome (object payload)', () => {
|
||||
const value = { id: 1, name: 'Alice' };
|
||||
|
||||
bench('native-monad', () => { okSome(value); }, STANDARD);
|
||||
bench('ts-results-es (nested)', () => { TsOk(TsSome(value)); }, STANDARD);
|
||||
bench('oxide.ts (nested)', () => { OxOk(OxSome(value)); }, STANDARD);
|
||||
bench('fp-ts (nested)', () => { E.right(O.some(value)); }, STANDARD);
|
||||
bench('true-myth (nested)', () => { TmResult.ok(TmMaybe.just(value)); }, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// OkNone — vs nested Result<Option<T>, E>
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query construction — OkNone (singleton)', () => {
|
||||
bench('native-monad', () => { okNone(); }, STANDARD);
|
||||
bench('ts-results-es (nested)', () => { TsOk(TsNone); }, STANDARD);
|
||||
bench('oxide.ts (nested)', () => { OxOk(OxNone); }, STANDARD);
|
||||
bench('fp-ts (nested)', () => { E.right(O.none); }, STANDARD);
|
||||
bench('true-myth (nested)', () => { TmResult.ok(TmMaybe.nothing()); }, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ErrNone — vs nested Err
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query construction — ErrNone', () => {
|
||||
bench('native-monad', () => { errNone('fail'); }, STANDARD);
|
||||
bench('ts-results-es (nested)', () => { TsErr('fail'); }, STANDARD);
|
||||
bench('oxide.ts (nested)', () => { OxErr('fail'); }, STANDARD);
|
||||
bench('fp-ts (nested)', () => { E.left('fail'); }, STANDARD);
|
||||
bench('true-myth (nested)', () => { TmResult.err('fail'); }, STANDARD);
|
||||
});
|
||||
172
benchmarks/query/instance-methods.bench.ts
Normal file
172
benchmarks/query/instance-methods.bench.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { bench, describe } from 'vitest';
|
||||
import { STANDARD } from '../helpers/bench-options.js';
|
||||
import {
|
||||
okSome,
|
||||
okNone,
|
||||
errNone,
|
||||
E,
|
||||
O,
|
||||
pipe,
|
||||
} from '../helpers/imports.js';
|
||||
|
||||
const double = (x: number) => x * 2;
|
||||
const toUpper = (e: string) => e.toUpperCase();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// map — OkSome path
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query.map — OkSome path', () => {
|
||||
const nm = okSome(42);
|
||||
const fp = E.right(O.some(42));
|
||||
|
||||
bench('native-monad', () => { nm.map(double); }, STANDARD);
|
||||
bench('fp-ts (nested)', () => {
|
||||
pipe(fp, E.map(O.map(double)));
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// map — OkNone path (no-op)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query.map — OkNone path (no-op)', () => {
|
||||
const nm = okNone();
|
||||
const fp = E.right(O.none);
|
||||
|
||||
bench('native-monad', () => { nm.map(double); }, STANDARD);
|
||||
bench('fp-ts (nested)', () => {
|
||||
pipe(fp, E.map(O.map(double)));
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// map — ErrNone path (no-op)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query.map — ErrNone path (no-op)', () => {
|
||||
const nm = errNone('fail');
|
||||
const fp = E.left('fail');
|
||||
|
||||
bench('native-monad', () => { nm.map(double); }, STANDARD);
|
||||
bench('fp-ts (nested)', () => {
|
||||
pipe(fp, E.map(O.map(double)));
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// flatMap — OkSome path
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query.flatMap — OkSome path', () => {
|
||||
const nm = okSome(42);
|
||||
const fp = E.right(O.some(42));
|
||||
|
||||
bench('native-monad', () => { nm.flatMap(x => okSome(x * 2)); }, STANDARD);
|
||||
bench('fp-ts (nested)', () => {
|
||||
pipe(
|
||||
fp,
|
||||
E.flatMap(inner =>
|
||||
pipe(
|
||||
inner,
|
||||
O.match(
|
||||
() => E.right(O.none) as E.Either<string, O.Option<number>>,
|
||||
v => E.right(O.some(v * 2)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// mapErr — ErrNone path
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query.mapErr — ErrNone path', () => {
|
||||
const nm = errNone('fail');
|
||||
const fp = E.left('fail');
|
||||
|
||||
bench('native-monad', () => { nm.mapErr(toUpper); }, STANDARD);
|
||||
bench('fp-ts (nested)', () => {
|
||||
pipe(fp, E.mapLeft(toUpper));
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// match — three-way
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query.match — OkSome (three-way dispatch)', () => {
|
||||
const nm = okSome(42);
|
||||
const fp = E.right(O.some(42));
|
||||
|
||||
bench('native-monad', () => {
|
||||
nm.match(v => v * 2, () => 0, () => -1);
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts (nested, two match calls)', () => {
|
||||
pipe(
|
||||
fp,
|
||||
E.match(
|
||||
() => -1,
|
||||
O.match(() => 0, v => v * 2),
|
||||
),
|
||||
);
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
describe('Query.match — OkNone (three-way dispatch)', () => {
|
||||
const nm = okNone();
|
||||
const fp = E.right(O.none);
|
||||
|
||||
bench('native-monad', () => {
|
||||
nm.match(() => 1, () => 0, () => -1);
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts (nested)', () => {
|
||||
pipe(
|
||||
fp,
|
||||
E.match(
|
||||
() => -1,
|
||||
O.match(() => 0, () => 1),
|
||||
),
|
||||
);
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
describe('Query.match — ErrNone (three-way dispatch)', () => {
|
||||
const nm = errNone('fail');
|
||||
const fp = E.left('fail');
|
||||
|
||||
bench('native-monad', () => {
|
||||
nm.match(() => 1, () => 0, e => e.length);
|
||||
}, STANDARD);
|
||||
|
||||
bench('fp-ts (nested)', () => {
|
||||
pipe(
|
||||
fp,
|
||||
E.match(
|
||||
e => e.length,
|
||||
O.match(() => 0, () => 1),
|
||||
),
|
||||
);
|
||||
}, STANDARD);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// filter — OkSome path
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
describe('Query.filter — predicate passes', () => {
|
||||
const nm = okSome(42);
|
||||
|
||||
bench('native-monad', () => { nm.filter(x => x > 0); }, STANDARD);
|
||||
// No other library has a three-state filter
|
||||
});
|
||||
|
||||
describe('Query.filter — predicate fails', () => {
|
||||
const nm = okSome(-1);
|
||||
|
||||
bench('native-monad', () => { nm.filter(x => x > 0); }, STANDARD);
|
||||
});
|
||||
Reference in New Issue
Block a user