├── src ├── ascii.ts ├── clist.ts ├── hlist.ts ├── ixlist.ts ├── ixmap.ts ├── list.ts ├── qlist.ts ├── attrmap.ts ├── datamap.ts ├── multimap.ts ├── sequence.ts ├── global.type.ts ├── tuple.ts ├── global.ts ├── compare.ts ├── timer.test.ts ├── monad │ ├── sequence │ │ └── member │ │ │ ├── static │ │ │ ├── mplus.ts │ │ │ ├── mzero.ts │ │ │ ├── mempty.ts │ │ │ ├── pure.ts │ │ │ ├── return.ts │ │ │ ├── mappend.ts │ │ │ ├── concat.test.ts │ │ │ ├── sequence.test.ts │ │ │ ├── resume.test.ts │ │ │ ├── resume.ts │ │ │ ├── from.ts │ │ │ ├── sequence.ts │ │ │ ├── cycle.ts │ │ │ ├── zip.ts │ │ │ ├── random.ts │ │ │ ├── concat.ts │ │ │ ├── intersect.ts │ │ │ ├── mconcat.ts │ │ │ ├── from.test.ts │ │ │ ├── cycle.test.ts │ │ │ ├── difference.ts │ │ │ ├── union.ts │ │ │ ├── random.test.ts │ │ │ ├── zip.test.ts │ │ │ ├── intersect.test.ts │ │ │ ├── union.test.ts │ │ │ └── difference.test.ts │ │ │ └── instance │ │ │ ├── sort.ts │ │ │ ├── bind.ts │ │ │ ├── join.ts │ │ │ ├── unique.ts │ │ │ ├── unique.test.ts │ │ │ ├── join.test.ts │ │ │ ├── tails.ts │ │ │ ├── inits.ts │ │ │ ├── extract.ts │ │ │ ├── memoize.test.ts │ │ │ ├── fmap.ts │ │ │ ├── drop.ts │ │ │ ├── map.ts │ │ │ ├── takeWhile.ts │ │ │ ├── dropUntil.ts │ │ │ ├── dropWhile.ts │ │ │ ├── takeUntil.ts │ │ │ ├── filter.ts │ │ │ ├── take.ts │ │ │ ├── reduce.ts │ │ │ ├── inits.test.ts │ │ │ ├── tails.test.ts │ │ │ ├── sort.test.ts │ │ │ ├── segs.ts │ │ │ ├── group.ts │ │ │ ├── memoize.ts │ │ │ ├── take.test.ts │ │ │ ├── reduce.test.ts │ │ │ ├── foldr.ts │ │ │ ├── mapM.ts │ │ │ ├── scanl.ts │ │ │ ├── segs.test.ts │ │ │ ├── map.test.ts │ │ │ ├── fmap.test.ts │ │ │ ├── filter.test.ts │ │ │ ├── iterate.ts │ │ │ ├── filterM.ts │ │ │ ├── permutations.test.ts │ │ │ ├── subsequences.test.ts │ │ │ ├── iterate.test.ts │ │ │ ├── group.test.ts │ │ │ ├── foldr.test.ts │ │ │ ├── ap.ts │ │ │ ├── scanl.test.ts │ │ │ ├── drop.test.ts │ │ │ ├── subsequences.ts │ │ │ ├── bind.test.ts │ │ │ ├── takeUntil.test.ts │ │ │ ├── dropWhile.test.ts │ │ │ ├── dropUntil.test.ts │ │ │ └── takeWhile.test.ts │ ├── monadplus.ts │ ├── lazy.ts │ ├── functor.ts │ ├── maybe.ts │ ├── monad.ts │ ├── either.ts │ ├── applicative.ts │ └── sequence.ts ├── global.test.ts ├── tuple.test.ts ├── dict.ts ├── helper │ ├── compose.ts │ └── compose.test.ts ├── sort.ts ├── flip.test.ts ├── statistics.ts ├── index.ts ├── function.test.ts ├── counter.test.ts ├── exception.ts ├── flip.ts ├── stack.test.ts ├── stack.ts ├── function.ts ├── alias.test.ts ├── uuid.test.ts ├── cofetch.test.ts ├── memoize.test.ts ├── copropagator.ts ├── dict │ ├── attrmap.ts │ ├── ixmap.ts │ ├── attrmap.test.ts │ ├── multimap.test.ts │ ├── multimap.ts │ └── datamap.ts ├── list │ ├── clist.test.ts │ ├── list.test.ts │ ├── hlist.ts │ ├── qlist.ts │ └── hlist.test.ts ├── uuid.ts ├── chrono.ts ├── future.test.ts ├── counter.ts ├── colistener.ts ├── duff.test.ts ├── ascii.hpack.test.ts ├── ascii.random.test.ts ├── ascii.xpack.test.ts ├── ascii.huffman.test.ts ├── coaggregator.ts ├── channel.test.ts ├── copropagator.test.ts ├── select.ts ├── alias.ts ├── sort.test.ts ├── tlru.ts ├── future.ts ├── curry.test.ts ├── chrono.test.ts ├── curry.ts ├── ascii.percent.ts ├── throttle.test.ts ├── timer.ts └── queue.test.ts ├── .gitconfig ├── CHANGELOG.md ├── .gitignore ├── module.test.d.ts ├── global.test.d.ts ├── benchmark ├── uuid.ts ├── generator.ts ├── benchmark.ts ├── alias.ts ├── counter.ts ├── supervisor.ts ├── maybe.ts ├── either.ts ├── type.ts ├── map.ts ├── inline.ts ├── coroutine.ts ├── url.ts ├── duff.ts ├── chrono.ts ├── channel.ts ├── cancellation.ts ├── memoize.ts ├── array.ts ├── ascii.ts └── sequence.ts ├── NOTICE.md ├── .eslintrc.json ├── README.md ├── tsconfig.json ├── karma.conf.js └── .github └── workflows └── main.yaml /src/ascii.ts: -------------------------------------------------------------------------------- 1 | export * from './ascii.delta'; 2 | -------------------------------------------------------------------------------- /src/clist.ts: -------------------------------------------------------------------------------- 1 | export * from './list/clist'; 2 | -------------------------------------------------------------------------------- /src/hlist.ts: -------------------------------------------------------------------------------- 1 | export * from './list/hlist'; 2 | -------------------------------------------------------------------------------- /src/ixlist.ts: -------------------------------------------------------------------------------- 1 | export * from './list/ixlist'; 2 | -------------------------------------------------------------------------------- /src/ixmap.ts: -------------------------------------------------------------------------------- 1 | export * from './dict/ixmap'; 2 | -------------------------------------------------------------------------------- /src/list.ts: -------------------------------------------------------------------------------- 1 | export * from './list/list'; 2 | -------------------------------------------------------------------------------- /src/qlist.ts: -------------------------------------------------------------------------------- 1 | export * from './list/qlist'; 2 | -------------------------------------------------------------------------------- /src/attrmap.ts: -------------------------------------------------------------------------------- 1 | export * from './dict/attrmap'; 2 | -------------------------------------------------------------------------------- /src/datamap.ts: -------------------------------------------------------------------------------- 1 | export * from './dict/datamap'; 2 | -------------------------------------------------------------------------------- /src/multimap.ts: -------------------------------------------------------------------------------- 1 | export * from './dict/multimap'; 2 | -------------------------------------------------------------------------------- /src/sequence.ts: -------------------------------------------------------------------------------- 1 | export * from './monad/sequence'; 2 | -------------------------------------------------------------------------------- /.gitconfig: -------------------------------------------------------------------------------- 1 | [merge] 2 | # from 1.7.6 3 | ff = false 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.0.x 4 | 5 | WIP 6 | -------------------------------------------------------------------------------- /src/global.type.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import global = globalThis; 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # binary 2 | coverage 3 | dist/**/* 4 | 5 | # node 6 | node_modules 7 | debug.log 8 | -------------------------------------------------------------------------------- /src/tuple.ts: -------------------------------------------------------------------------------- 1 | export function tuple(...as: as): as { 2 | return as; 3 | } 4 | -------------------------------------------------------------------------------- /src/global.ts: -------------------------------------------------------------------------------- 1 | import './global.type'; 2 | 3 | const global = globalThis; 4 | global.global = global; 5 | 6 | export default global; 7 | -------------------------------------------------------------------------------- /src/compare.ts: -------------------------------------------------------------------------------- 1 | export function equal(a: unknown, b: unknown): boolean { 2 | return a === a 3 | ? a === b 4 | : b !== b; 5 | } 6 | -------------------------------------------------------------------------------- /module.test.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'zipfian-integer' { 2 | export default function ( 3 | min: number, 4 | max: number, 5 | skew: number, 6 | rng?: () => number, 7 | ): () => number; 8 | } 9 | -------------------------------------------------------------------------------- /global.test.d.ts: -------------------------------------------------------------------------------- 1 | import assert from 'power-assert'; 2 | 3 | declare namespace NS { 4 | export { 5 | assert, 6 | } 7 | } 8 | 9 | declare global { 10 | const assert: typeof NS.assert; 11 | } 12 | -------------------------------------------------------------------------------- /src/timer.test.ts: -------------------------------------------------------------------------------- 1 | import { setTimer } from './timer'; 2 | 3 | describe('Unit: lib/timer', () => { 4 | describe('setTimet', () => { 5 | it('', (done) => { 6 | setTimer(100, done) 7 | }); 8 | 9 | }); 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/mplus.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override mplus = Sequence.mappend; 6 | }); 7 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/mzero.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override mzero = Sequence.mempty; 6 | }); 7 | -------------------------------------------------------------------------------- /src/global.test.ts: -------------------------------------------------------------------------------- 1 | import global from './global'; 2 | 3 | describe('Unit: lib/global', () => { 4 | describe('global', () => { 5 | it('', () => { 6 | assert(global === globalThis); 7 | assert(global.global === globalThis); 8 | }); 9 | }); 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /src/monad/monadplus.ts: -------------------------------------------------------------------------------- 1 | import { Monad } from './monad'; 2 | 3 | export abstract class MonadPlus extends Monad { 4 | } 5 | export namespace MonadPlus { 6 | export declare const mzero: MonadPlus; 7 | export declare function mplus(ml: MonadPlus, mr: MonadPlus): MonadPlus; 8 | } 9 | -------------------------------------------------------------------------------- /src/tuple.test.ts: -------------------------------------------------------------------------------- 1 | import { tuple } from './tuple'; 2 | 3 | describe('Unit: lib/tuple', () => { 4 | describe('tuple', () => { 5 | it('', () => { 6 | assert((): [number] => tuple(0)); 7 | assert((): [number, number] => tuple(0, 0)); 8 | }); 9 | 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/mempty.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override mempty: Sequence = new Sequence((_, cons) => cons()); 6 | }); 7 | -------------------------------------------------------------------------------- /benchmark/uuid.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { uuid } from '../src/uuid'; 3 | 4 | describe('Benchmark:', function () { 5 | describe('uuid', function () { 6 | it('gen', function (done) { 7 | benchmark('uuid gen', uuid, done); 8 | }); 9 | 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /src/monad/lazy.ts: -------------------------------------------------------------------------------- 1 | export abstract class Lazy { 2 | constructor(protected readonly thunk: () => Lazy) { 3 | } 4 | private $memory?: this = undefined; 5 | protected evaluate(): this { 6 | return this.$memory ??= this.thunk() as this; 7 | } 8 | public abstract extract(): unknown; 9 | } 10 | -------------------------------------------------------------------------------- /src/monad/functor.ts: -------------------------------------------------------------------------------- 1 | import { Lazy } from './lazy'; 2 | 3 | export abstract class Functor extends Lazy { 4 | abstract fmap(f: (a: a) => b): Functor; 5 | } 6 | export namespace Functor { 7 | export function fmap(f: (a: a) => b, m: Functor): Functor { 8 | return m.fmap(f); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/pure.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override pure(a: a): Sequence { 6 | return new Sequence((_, cons) => cons(a)); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/return.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override Return(a: a): Sequence { 6 | return new Sequence((_, cons) => cons(a)); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/sort.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override sort(cmp?: (a: a, b: a) => number): Sequence>]> { 6 | return Sequence.from(this.extract().sort(cmp)); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/bind.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override bind(f: (a: a) => Sequence): Sequence>, Sequence.Iterator]> { 6 | return Sequence.concat(this.fmap(f)); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/mappend.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override mappend(l: Sequence, r: Sequence): Sequence, Sequence.Iterator]> { 6 | return Sequence.mconcat([l, r]); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/join.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override join(this: Sequence, unknown>): Sequence>, Sequence.Iterator]> { 6 | return Sequence.concat(this); 7 | } 8 | }); 9 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/unique.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override unique(): Sequence> { 6 | const memory = new Set(); 7 | return this.filter(a => 8 | !memory.has(a) && !!memory.add(a)); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/unique.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | 3 | describe('Unit: lib/monad/sequence/member/unique', () => { 4 | describe('unique', () => { 5 | it('', () => { 6 | assert.deepStrictEqual( 7 | Sequence.from([1, 2, 3, 3, 2, 4]) 8 | .unique() 9 | .extract(), 10 | [1, 2, 3, 4]); 11 | }); 12 | 13 | }); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/join.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | 3 | describe('Unit: lib/monad/sequence/member/join', () => { 4 | describe('join', () => { 5 | it('', () => { 6 | assert.deepStrictEqual( 7 | Sequence.from([Sequence.from([0, 1]), Sequence.from([2, 3])]) 8 | .join() 9 | .extract(), 10 | [0, 1, 2, 3]); 11 | }); 12 | 13 | }); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /src/monad/maybe.ts: -------------------------------------------------------------------------------- 1 | import * as Monad from './maybe.impl'; 2 | import { noop } from '../function'; 3 | 4 | export class Maybe extends Monad.Maybe { 5 | private constructor() { 6 | super(noop as never); 7 | } 8 | } 9 | 10 | export type Just = Monad.Just; 11 | export function Just(a: a): Maybe { 12 | return new Monad.Just(a); 13 | } 14 | export type Nothing = Monad.Nothing; 15 | export const Nothing = Monad.Maybe.mzero; 16 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/tails.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override tails(): Sequence, Sequence.Iterator]> { 6 | return Sequence.mappend( 7 | Sequence.from(this.extract().map((_, i, as) => as.slice(i))), 8 | Sequence.from([[]])); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/concat.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | 3 | describe('Unit: lib/monad/sequence/member/static/concat', () => { 4 | describe('Sequence.concat', () => { 5 | it('Sequence, S>', () => { 6 | assert.deepStrictEqual( 7 | Sequence.concat(Sequence.from([Sequence.from([0, 1, 2])])) 8 | .take(2) 9 | .extract(), 10 | [0, 1]); 11 | }); 12 | 13 | }); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/sequence.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | 3 | describe('Unit: lib/monad/sequence/member/static/sequence', () => { 4 | describe('Sequence.sequence', () => { 5 | it('', () => { 6 | assert.deepStrictEqual( 7 | Sequence.sequence([Sequence.from([0, 1]), Sequence.from([2, 3])]) 8 | .map(m => m.extract()) 9 | .extract(), 10 | [[0, 1, 2, 3]]); 11 | }); 12 | 13 | }); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /src/dict.ts: -------------------------------------------------------------------------------- 1 | // WARNING: Must not contain assertions here and in dependency modules! 2 | 3 | type NonNull = {}; 4 | 5 | export interface Dict { 6 | add?(key: K, value: V): NonNull; 7 | set(key: K, value: V): this; 8 | get(key: K): V | undefined; 9 | has(key: K): boolean; 10 | delete(key: K): boolean; 11 | } 12 | 13 | export interface IterableDict extends Dict { 14 | readonly size: number; 15 | [Symbol.iterator](): Iterator<[K, V], undefined, undefined>; 16 | } 17 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/inits.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override inits(): Sequence, Sequence.Iterator]> { 6 | return Sequence.mappend( 7 | Sequence.from([[]]), 8 | this 9 | .scanl(((b, a) => [...b, a]), []) 10 | .dropWhile(as => as.length === 0)); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /src/helper/compose.ts: -------------------------------------------------------------------------------- 1 | export function compose object>(target: T, ...sources: T[]): T { 2 | return sources 3 | .reduce((b, d) => { 4 | Object.getOwnPropertyNames(d.prototype) 5 | .filter(p => !(p in b.prototype)) 6 | .forEach(p => b.prototype[p] = d.prototype[p]); 7 | Object.getOwnPropertyNames(d) 8 | .filter(p => !(p in b)) 9 | .forEach(p => b[p] = d[p]); 10 | return b; 11 | } 12 | , target); 13 | } 14 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/resume.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { nat } from '../../../sequence.test'; 3 | 4 | describe('Unit: lib/monad/sequence/member/static/resume', () => { 5 | describe('Sequence.resume', () => { 6 | it('nat', () => { 7 | assert.deepStrictEqual( 8 | Sequence.resume(Sequence.Thunk.iterator(nat.iterate())) 9 | .take(3) 10 | .extract(), 11 | [1, 2, 3]); 12 | }); 13 | 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /src/sort.ts: -------------------------------------------------------------------------------- 1 | export function sort(as: T[], cmp: (a: T, b: T) => number, times: number, debug = false): T[] { 2 | if (!debug && times * times > as.length * 1.25) return as.sort(cmp); 3 | times = times < as.length - 1 ? times : as.length - 1; 4 | for (let i = 0; i < times; ++i) { 5 | for (let j = i + 1; j < as.length; ++j) { 6 | if (cmp(as[i], as[j]) > 0 === false) continue; 7 | const a = as[i]; 8 | as[i] = as[j]; 9 | as[j] = a; 10 | } 11 | } 12 | return as; 13 | } 14 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/extract.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override extract(): a[] { 6 | const acc: a[] = []; 7 | let iter = () => this.iterate(); 8 | while (true) { 9 | const thunk = iter(); 10 | if (!Sequence.isIterable(thunk)) return acc; 11 | acc.push(Sequence.Thunk.value(thunk)); 12 | iter = Sequence.Thunk.iterator(thunk); 13 | } 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/memoize.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | 3 | describe('Unit: lib/monad/sequence/member/memoize', () => { 4 | describe('memoize', () => { 5 | it('permanent', () => { 6 | let cnt = 0; 7 | const mem = Sequence.cycle([() => ++cnt]) 8 | .map(f => f()) 9 | .memoize(); 10 | assert.deepStrictEqual(mem.take(1).bind(() => mem.take(2)).extract(), [1, 2]); 11 | assert.deepStrictEqual(mem.take(2).extract(), [1, 2]); 12 | }); 13 | 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /src/flip.test.ts: -------------------------------------------------------------------------------- 1 | import { flip } from './flip'; 2 | import { curry } from './curry'; 3 | 4 | describe('Unit: lib/flip', () => { 5 | describe('flip', () => { 6 | it('uncurried', () => { 7 | assert(flip((n: number, m: number) => n / m)(1, 0) === 0); 8 | }); 9 | 10 | it('curried', () => { 11 | assert(flip(curry((n: number, m: number) => n / m))(1)(0) === 0); 12 | }); 13 | 14 | it('generic', () => { 15 | assert(flip((n: T, m: U) => n / m)(1, 0) === 0); 16 | }); 17 | 18 | }); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /src/statistics.ts: -------------------------------------------------------------------------------- 1 | export function sum(values: readonly number[]): number { 2 | return values.reduce((a, b) => a + b, 0); 3 | } 4 | 5 | export function average(values: readonly number[]): number { 6 | return sum(values) / values.length; 7 | } 8 | 9 | export function distribution(values: readonly number[]): number { 10 | const avg = average(values); 11 | return values.reduce((a, b) => a + Math.pow(b - avg, 2), 0) / values.length; 12 | } 13 | 14 | export function deviation(values: readonly number[]): number { 15 | return Math.sqrt(distribution(values)); 16 | } 17 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from './stack'; 2 | 3 | export class Index { 4 | private readonly stack = new Stack(); 5 | private $length = 0; 6 | public get length(): number { 7 | return this.$length; 8 | } 9 | public push(index: number): void { 10 | this.stack.push(index); 11 | --this.$length; 12 | } 13 | public pop(): number { 14 | const index = this.stack.pop() ?? this.$length; 15 | ++this.$length; 16 | return index; 17 | } 18 | public clear(): void { 19 | this.stack.clear(); 20 | this.$length = 0; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/resume.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override resume(iterator: Sequence.Iterator): Sequence> { 6 | return new Sequence>((iter = iterator, cons) => 7 | Sequence.Iterator.when( 8 | iter(), 9 | () => cons(), 10 | thunk => cons(Sequence.Thunk.value(thunk), Sequence.Thunk.iterator(thunk)))); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /NOTICE.md: -------------------------------------------------------------------------------- 1 | # Notice 2 | 3 | You must read this notice before distributing any copies of files of this project or module. 4 | 5 | ## Source 6 | 7 | The source project is as follows: 8 | 9 | https://github.com/falsandtru/spica 10 | 11 | ## License 12 | 13 | This project or module is dual-licensed under the Mozilla Public License 2.0 and the Apache License 2.0. 14 | 15 | ### Mozilla Public License 2.0 16 | 17 | This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 18 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": [ 4 | //"eslint:recommended", 5 | //"plugin:@typescript-eslint/recommended" 6 | ], 7 | "parser": "@typescript-eslint/parser", 8 | "parserOptions": { "project": ["./tsconfig.json"] }, 9 | "plugins": [ 10 | //"@typescript-eslint", 11 | "redos" 12 | ], 13 | "rules": { 14 | "redos/no-vulnerable": [ 15 | "error", 16 | { 17 | "ignoreErrors": false, 18 | "attackTimeout": null, 19 | "timeout": 30000 20 | } 21 | ] 22 | }, 23 | "ignorePatterns": [ 24 | "**/*.test.ts" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /benchmark/generator.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | 3 | describe('Benchmark:', function () { 4 | describe('Generator', function () { 5 | function f(as: Iterable) { 6 | for (const a of as) a; 7 | } 8 | function* g(len: number) { 9 | for (let i = 0; i < len; ++i) { 10 | yield i; 11 | } 12 | } 13 | 14 | for (const size of [1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6]) { 15 | it(size.toLocaleString('en'), function (done) { 16 | benchmark(`Generator ${size.toLocaleString('en')}`, () => f(g(size)), done); 17 | }); 18 | } 19 | }); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/from.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override from(as: Iterable): Sequence>]> { 6 | return new Sequence, number]>( 7 | ([iter, i] = [as[Symbol.iterator](), 0], cons) => { 8 | const result = iter.next(); 9 | return result.done 10 | ? cons() 11 | : cons(result.value, [iter, i + 1]); 12 | }) 13 | .reduce(); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/function.test.ts: -------------------------------------------------------------------------------- 1 | import { singleton, clear } from './function'; 2 | 3 | describe('Unit: lib/function', function () { 4 | describe('singleton', function () { 5 | it('', () => { 6 | let cnt = 0; 7 | const f = singleton(() => ++cnt); 8 | assert(cnt === 0); 9 | assert(f() === 1); 10 | assert(cnt === 1); 11 | assert(f() === 1); 12 | assert(cnt === 1); 13 | }); 14 | 15 | }); 16 | 17 | describe('clear', function () { 18 | it('', () => { 19 | assert.deepStrictEqual( 20 | clear(() => 0)(), 21 | undefined); 22 | }); 23 | 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/fmap.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override fmap(f: (a: a) => b): Sequence> { 6 | return new Sequence>((iter = () => this.iterate()) => 7 | Sequence.Iterator.when>>( 8 | iter(), 9 | () => Sequence.Data.cons(), 10 | thunk => Sequence.Data.cons(f(Sequence.Thunk.value(thunk)), Sequence.Thunk.iterator(thunk)))); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /src/counter.test.ts: -------------------------------------------------------------------------------- 1 | import { counter } from './counter'; 2 | 3 | describe('Unit: lib/counter', () => { 4 | describe('counter', () => { 5 | for (const radix of [10, 16, 32, 36]) { 6 | it(`${radix}`, () => { 7 | const count = counter(radix); 8 | for (let i = 1; i < 1e5; ++i) { 9 | assert(count() === i.toString(radix)); 10 | } 11 | }); 12 | } 13 | 14 | it('pad', () => { 15 | const count = counter(10, '00'); 16 | for (let i = 1; i < 1000; ++i) { 17 | assert(count() === i.toString(10).padStart(2, '0')); 18 | } 19 | }); 20 | 21 | }); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /src/exception.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from './stack'; 2 | 3 | const stack = new Stack(); 4 | 5 | export function causeAsyncException(reason: unknown): void { 6 | if (stack.isEmpty()) { 7 | assert(!+console.error(reason)); 8 | assert(eval('throw reason')); 9 | Promise.reject(reason); 10 | } 11 | else { 12 | stack.peek()!.push(reason); 13 | } 14 | } 15 | export function suppressAsyncException void) => unknown>(test: f): f { 16 | return (done => { 17 | stack.push([]); 18 | return test(err => { 19 | stack.pop(); 20 | done(err); 21 | }); 22 | }) as f; 23 | } 24 | -------------------------------------------------------------------------------- /src/monad/monad.ts: -------------------------------------------------------------------------------- 1 | import { Applicative } from './applicative'; 2 | 3 | export abstract class Monad extends Applicative { 4 | public abstract override bind(f: (a: a) => Monad): Monad; 5 | public abstract join(this: Monad>): Monad; 6 | } 7 | export namespace Monad { 8 | export declare function Return(a: a): Monad; 9 | export function bind(f: (a: a) => Monad, m: Monad): Monad { 10 | return m.bind(f); 11 | } 12 | export declare function sequence(fm: Monad[]): Monad>; 13 | //export declare function sequence(fm: Monad>): AtomicPromise>; 14 | } 15 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/drop.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override drop(n: number): Sequence> { 6 | return new Sequence>((iter = () => this.iterate(), cons) => 7 | Sequence.Iterator.when( 8 | iter(), 9 | () => cons(), 10 | (thunk, recur) => 11 | Sequence.Thunk.index(thunk) < n 12 | ? recur() 13 | : cons(Sequence.Thunk.value(thunk), Sequence.Thunk.iterator(thunk)))); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/map.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override map(f: (a: a, i: number) => b): Sequence> { 6 | return new Sequence>((iter = () => this.iterate()) => 7 | Sequence.Iterator.when>>( 8 | iter(), 9 | () => Sequence.Data.cons(), 10 | thunk => Sequence.Data.cons(f(Sequence.Thunk.value(thunk), Sequence.Thunk.index(thunk)), Sequence.Thunk.iterator(thunk)))); 11 | } 12 | }); 13 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/takeWhile.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override takeWhile(f: (a: a) => boolean): Sequence> { 6 | return new Sequence>((iter = () => this.iterate(), cons) => 7 | Sequence.Iterator.when( 8 | iter(), 9 | () => cons(), 10 | thunk => 11 | f(Sequence.Thunk.value(thunk)) 12 | ? cons(Sequence.Thunk.value(thunk), Sequence.Thunk.iterator(thunk)) 13 | : cons())); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/dropUntil.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override dropUntil(f: (a: a) => boolean): Sequence> { 6 | return new Sequence>((iter = () => this.iterate(), cons) => 7 | Sequence.Iterator.when( 8 | iter(), 9 | () => cons(), 10 | (thunk, recur) => 11 | f(Sequence.Thunk.value(thunk)) 12 | ? recur() 13 | : cons(Sequence.Thunk.value(thunk), Sequence.Thunk.iterator(thunk)))); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/dropWhile.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override dropWhile(f: (a: a) => boolean): Sequence> { 6 | return new Sequence>((iter = () => this.iterate(), cons) => 7 | Sequence.Iterator.when( 8 | iter(), 9 | () => cons(), 10 | (thunk, recur) => 11 | f(Sequence.Thunk.value(thunk)) 12 | ? recur() 13 | : cons(Sequence.Thunk.value(thunk), Sequence.Thunk.iterator(thunk)))); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/flip.ts: -------------------------------------------------------------------------------- 1 | import { Curried2 } from './curry'; 2 | 3 | export function flip(f: (a: a) => (b: b) => c): (b: b) => (a: a) => c; 4 | export function flip(f: (a: a, b: b) => c): (b: b, a: a) => c; 5 | export function flip(f: ((a: a, b: b) => c) | ((a: a) => (b: b) => c)): Curried2; 6 | export function flip(f: ((a: a, b: b) => c) | ((a: a) => (b: b) => c)): any { 7 | const arity = f.length; 8 | return arity > 1 9 | ? (b: b, a: a) => f(a, b) as c 10 | : (b: b, ...as: a[]) => 11 | as.length === 0 12 | ? (a: a) => (<(a: a) => (b: b) => c>f)(a)(b) 13 | : (<(a: a) => (b: b) => c>f)(as[0])(b) as c; 14 | } 15 | -------------------------------------------------------------------------------- /benchmark/benchmark.ts: -------------------------------------------------------------------------------- 1 | import Benchmark from 'benchmark'; 2 | 3 | mocha.setup({ 4 | timeout: 300 * 1e3, 5 | rootHooks: { 6 | beforeEach: () => gc!(), 7 | }, 8 | }); 9 | 10 | export function benchmark(name: string, proc: (done: () => void) => unknown, done: (err?: unknown) => void, options: Benchmark.Options = {}) { 11 | new Benchmark.Suite() 12 | .add({ 13 | minSamples: 60, 14 | async: true, 15 | ...options, 16 | name, 17 | fn: (d: any) => proc(() => d.resolve()), 18 | }) 19 | .on('cycle', function (event: Event) { 20 | console.log(String(event.target)); 21 | }) 22 | .on('complete', () => done()) 23 | .run(); 24 | } 25 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/takeUntil.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override takeUntil(f: (a: a) => boolean): Sequence> { 6 | return new Sequence>((iter = () => this.iterate(), cons) => 7 | Sequence.Iterator.when( 8 | iter(), 9 | () => cons(), 10 | thunk => 11 | f(Sequence.Thunk.value(thunk)) 12 | ? cons(Sequence.Thunk.value(thunk)) 13 | : cons(Sequence.Thunk.value(thunk), Sequence.Thunk.iterator(thunk)))); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/stack.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from './stack'; 2 | 3 | describe('Unit: lib/stack', () => { 4 | describe('Stack', () => { 5 | it('push/pop', () => { 6 | const stack = new Stack(); 7 | assert(stack.pop() === undefined); 8 | assert(stack.push(0) === undefined); 9 | assert(stack.pop() === 0); 10 | assert(stack.pop() === undefined); 11 | assert(stack.push(0) === undefined); 12 | assert(stack.push(1) === undefined); 13 | assert(stack.pop() === 1); 14 | assert(stack.push(2) === undefined); 15 | assert(stack.pop() === 2); 16 | assert(stack.pop() === 0); 17 | assert(stack.pop() === undefined); 18 | }); 19 | }); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /src/monad/either.ts: -------------------------------------------------------------------------------- 1 | import * as Monad from './either.impl'; 2 | import { noop } from '../function'; 3 | 4 | export class Either extends Monad.Either { 5 | private constructor() { 6 | super(noop as never); 7 | } 8 | } 9 | 10 | export type Left = Monad.Left; 11 | export function Left(a: a): Left 12 | export function Left(a: a): Either 13 | export function Left(a: a): Either { 14 | return new Monad.Left(a); 15 | } 16 | export type Right = Monad.Right; 17 | export function Right(b: b): Right 18 | export function Right(b: b): Either 19 | export function Right(b: b): Either { 20 | return new Monad.Right(b); 21 | } 22 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/filter.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override filter(f: (a: a, i: number) => boolean): Sequence> { 6 | return new Sequence>((iter = () => this.iterate(), cons) => 7 | Sequence.Iterator.when( 8 | iter(), 9 | () => cons(), 10 | (thunk, recur) => 11 | f(Sequence.Thunk.value(thunk), Sequence.Thunk.index(thunk)) 12 | ? cons(Sequence.Thunk.value(thunk), Sequence.Thunk.iterator(thunk)) 13 | : recur())); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/take.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override take(n: number): Sequence> { 6 | return new Sequence>((iter = () => this.iterate(), cons) => 7 | Sequence.Iterator.when( 8 | n > 0 ? iter() : >Sequence.Iterator.done(), 9 | () => cons(), 10 | thunk => 11 | Sequence.Thunk.index(thunk) + 1 < n 12 | ? cons(Sequence.Thunk.value(thunk), Sequence.Thunk.iterator(thunk)) 13 | : cons(Sequence.Thunk.value(thunk)))); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/reduce.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override reduce(): Sequence>]> { 6 | return new Sequence>]>( 7 | ([i, memo] = [0, new Map>()], cons) => 8 | Sequence.Iterator.when( 9 | memo.get(i) || memo.set(i, i > 0 && memo.has(i - 1) ? Sequence.Thunk.iterator(memo.get(i - 1)!)() : this.iterate()).get(i)!, 10 | () => cons(), 11 | thunk => cons(Sequence.Thunk.value(thunk), [i + 1, memo]))); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /src/stack.ts: -------------------------------------------------------------------------------- 1 | export class Stack { 2 | private array: T[] = []; 3 | public get length(): number { 4 | return this.array.length; 5 | } 6 | public isEmpty(): boolean { 7 | return this.array.length === 0; 8 | } 9 | public peek(index: 0 | -1 = 0): T | undefined { 10 | return index === 0 11 | ? this.array.at(-1) 12 | : this.array[0]; 13 | } 14 | public push(value: T): void { 15 | this.array.push(value); 16 | } 17 | public pop(): T | undefined { 18 | return this.array.pop(); 19 | } 20 | public clear(): void { 21 | this.array = []; 22 | } 23 | public *[Symbol.iterator](): Iterator { 24 | while (!this.isEmpty()) { 25 | yield this.pop()!; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/sequence.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override sequence(ms: Sequence[]): Sequence, Sequence.Iterator]>, Sequence.Iterator, Sequence.Iterator]>>> { 6 | return ms.reduce, Sequence.Iterator]>, Sequence.Iterator, Sequence.Iterator]>>>>((acc, m) => 7 | acc.fmap(bs => 8 | Sequence.mappend(bs, m)) 9 | , Sequence.Return(Sequence.from([])) as Sequence, any>); 10 | } 11 | }); 12 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/inits.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | 3 | describe('Unit: lib/monad/sequence/member/inits', () => { 4 | describe('inits', () => { 5 | it('0', () => { 6 | assert.deepStrictEqual( 7 | Sequence.from([]) 8 | .inits() 9 | .extract(), 10 | [[]]); 11 | }); 12 | 13 | it('1', () => { 14 | assert.deepStrictEqual( 15 | Sequence.from([1]) 16 | .inits() 17 | .extract(), 18 | [[], [1]]); 19 | }); 20 | 21 | it('2', () => { 22 | assert.deepStrictEqual( 23 | Sequence.from([1, 2]) 24 | .inits() 25 | .extract(), 26 | [[], [1], [1, 2]]); 27 | }); 28 | 29 | }); 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/tails.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | 3 | describe('Unit: lib/monad/sequence/member/tails', () => { 4 | describe('tails', () => { 5 | it('0', () => { 6 | assert.deepStrictEqual( 7 | Sequence.from([]) 8 | .tails() 9 | .extract(), 10 | [[]]); 11 | }); 12 | 13 | it('1', () => { 14 | assert.deepStrictEqual( 15 | Sequence.from([1]) 16 | .tails() 17 | .extract(), 18 | [[1], []]); 19 | }); 20 | 21 | it('2', () => { 22 | assert.deepStrictEqual( 23 | Sequence.from([1, 2]) 24 | .tails() 25 | .extract(), 26 | [[1, 2], [2], []]); 27 | }); 28 | 29 | }); 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/cycle.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override cycle(as: Iterable): Sequence>]> { 6 | return new Sequence, number]>( 7 | function cycle( 8 | [iter, i] = [as[Symbol.iterator](), 0], 9 | cons, 10 | ): Sequence.Data, number]> { 11 | const result = iter.next(); 12 | return result.done 13 | ? cycle([as[Symbol.iterator](), i + 1], cons) 14 | : cons(result.value, [iter, i + 1] as [Iterator, number]); 15 | }) 16 | .reduce(); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/sort.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | 3 | describe('Unit: lib/monad/sequence/member/sort', () => { 4 | describe('sort', () => { 5 | it('empty', () => { 6 | assert.deepStrictEqual( 7 | Sequence.from([]) 8 | .sort() 9 | .extract(), 10 | []); 11 | }); 12 | 13 | it('sorted', () => { 14 | assert.deepStrictEqual( 15 | Sequence.from([0, 1, 2]) 16 | .sort() 17 | .extract(), 18 | [0, 1, 2]); 19 | }); 20 | 21 | it('unsorted', () => { 22 | assert.deepStrictEqual( 23 | Sequence.from([2, 0, 1]) 24 | .sort() 25 | .extract(), 26 | [0 ,1, 2]); 27 | }); 28 | 29 | }); 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /benchmark/alias.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { ObjectGetPrototypeOf, hasOwnProperty } from '../src/alias'; 3 | 4 | describe('Benchmark:', function () { 5 | describe('ObjectGetPrototypeOf', function () { 6 | it('', function (done) { 7 | const obj = {}; 8 | benchmark('alias ObjectGetPrototypeOf', () => ObjectGetPrototypeOf(obj), done); 9 | }); 10 | 11 | }); 12 | 13 | describe('hasOwnProperty', function () { 14 | const obj = { 0: 0 }; 15 | 16 | it('miss', function (done) { 17 | benchmark('alias hasOwnProperty miss', () => hasOwnProperty(obj, ''), done); 18 | }); 19 | 20 | it('match', function (done) { 21 | benchmark('alias hasOwnProperty match', () => hasOwnProperty(obj, '0'), done); 22 | }); 23 | 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /src/function.ts: -------------------------------------------------------------------------------- 1 | export function singleton unknown>(f: f): f { 2 | let result: unknown; 3 | assert(result === noop()); 4 | return function (this: unknown, ...as) { 5 | if (f === noop) return result; 6 | result = f.call(this, ...as); 7 | f = noop as f; 8 | return result; 9 | } as f; 10 | } 11 | 12 | export function clear(f: (...as: as) => void): (...as: as) => undefined { 13 | return (...as) => void f(...as); 14 | } 15 | 16 | export function id(a: a): a { 17 | return a; 18 | } 19 | 20 | export function fix(f: (a: a) => a): (a: a) => a { 21 | return a1 => { 22 | const a2 = f(a1); 23 | return a1 === a2 24 | || a2 !== a2 25 | ? a2 26 | : f(a2); 27 | }; 28 | } 29 | 30 | export function noop(): undefined { 31 | } 32 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/zip.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override zip(a: Sequence, b: Sequence): Sequence<[a, b], [Sequence.Iterator, Sequence.Iterator]> { 6 | return new Sequence<[a, b], [Sequence.Iterator, Sequence.Iterator]>(([ai, bi] = [() => a.iterate(), () => b.iterate()], cons) => 7 | Sequence.Iterator.when( 8 | ai(), 9 | () => cons(), 10 | at => 11 | Sequence.Iterator.when( 12 | bi(), 13 | () => cons(), 14 | bt => cons([Sequence.Thunk.value(at), Sequence.Thunk.value(bt)], [Sequence.Thunk.iterator(at), Sequence.Thunk.iterator(bt)])))); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/segs.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override segs(): Sequence, Sequence.Iterator]> { 6 | return Sequence.mappend( 7 | this 8 | .foldr>((a, bs) => 9 | bs.take(1) 10 | .bind(b => 11 | Sequence.mappend( 12 | Sequence.from([ 13 | Sequence.mappend( 14 | Sequence.from([[a]]), 15 | Sequence.from(b).map(c => [a, ...c])) 16 | ]), 17 | bs)) 18 | , Sequence.from([Sequence.from([])])) 19 | .bind(a => a), 20 | Sequence.from([[]])); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/group.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override group(f: (x: a, y: a) => boolean): Sequence, a[]]> { 6 | return new Sequence, a[]]>(([iter, acc] = [() => this.iterate(), []], cons) => 7 | Sequence.Iterator.when( 8 | iter(), 9 | () => 10 | acc.length === 0 11 | ? cons() 12 | : cons(acc), 13 | (thunk, recur) => 14 | acc.length === 0 || f(acc[0], Sequence.Thunk.value(thunk)) 15 | ? (acc.push(Sequence.Thunk.value(thunk)), recur()) 16 | : cons(acc, [Sequence.Thunk.iterator(thunk), [Sequence.Thunk.value(thunk)]]))); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/memoize.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | import { memoize } from '../../../../memoize'; 4 | 5 | const memory = memoize((_: Sequence): Map> => new Map()); 6 | 7 | compose(Sequence, class extends Sequence { 8 | public override memoize(): Sequence>]> { 9 | return new Sequence>]>( 10 | ([i, memo] = [0, memory(this)], cons) => 11 | Sequence.Iterator.when( 12 | memo.get(i) || memo.set(i, i > 0 && memo.has(i - 1) ? Sequence.Thunk.iterator(memo.get(i - 1)!)() : this.iterate()).get(i)!, 13 | () => cons(), 14 | thunk => cons(Sequence.Thunk.value(thunk), [i + 1, memo]))); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/take.test.ts: -------------------------------------------------------------------------------- 1 | import { nat } from '../../../sequence.test'; 2 | 3 | describe('Unit: lib/monad/sequence/member/take', () => { 4 | describe('take', () => { 5 | it('+0', () => { 6 | assert.deepStrictEqual( 7 | nat 8 | .take(0) 9 | .extract(), 10 | []); 11 | }); 12 | 13 | it('+1', () => { 14 | assert.deepStrictEqual( 15 | nat 16 | .take(1) 17 | .extract(), 18 | [0]); 19 | }); 20 | 21 | it('+2', () => { 22 | assert.deepStrictEqual( 23 | nat 24 | .take(2) 25 | .extract(), 26 | [0, 1]); 27 | }); 28 | 29 | it('+3', () => { 30 | assert.deepStrictEqual( 31 | nat 32 | .take(3) 33 | .extract(), 34 | [0, 1, 2]); 35 | }); 36 | 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/reduce.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | 3 | describe('Unit: lib/monad/sequence/member/reduce', () => { 4 | describe('reduce', () => { 5 | it('temporary', () => { 6 | let cnt = 0; 7 | const mem = Sequence.cycle([() => ++cnt]) 8 | .map(f => f()) 9 | .reduce(); 10 | assert.deepStrictEqual( 11 | mem 12 | .take(2) 13 | .foldr((a, b) => Sequence.from([a].concat(b.extract()).concat(b.extract())), Sequence.from([])) 14 | .extract(), 15 | [1, 2, 2]); 16 | assert.deepStrictEqual( 17 | mem 18 | .take(2) 19 | .foldr((a, b) => Sequence.from([a].concat(b.extract()).concat(b.extract())), Sequence.from([])) 20 | .extract(), 21 | [3, 4, 4]); 22 | }); 23 | 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/foldr.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override foldr(f: (a: a, b: Sequence) => Sequence, z: Sequence): Sequence>, Sequence.Iterator]> { 6 | return new Sequence, Sequence.Iterator>((iter = () => this.reduce().iterate()) => 7 | Sequence.Iterator.when( 8 | iter(), 9 | () => 10 | Sequence.Data.cons(z), 11 | thunk => 12 | Sequence.Data.cons( 13 | f( 14 | Sequence.Thunk.value(thunk), 15 | Sequence.resume(Sequence.Thunk.iterator(thunk)) 16 | .foldr(f, z))))) 17 | .bind(s => s); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/mapM.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override mapM(f: (a: a) => Sequence): Sequence>, Sequence.Iterator]> { 6 | return Sequence.from([0]) 7 | .bind(() => { 8 | const xs = this.extract(); 9 | switch (xs.length) { 10 | case 0: 11 | return Sequence.mempty; 12 | default: { 13 | const x = xs.shift()!; 14 | return f(x) 15 | .bind(y => 16 | xs.length === 0 17 | ? Sequence.from([[y]]) 18 | : Sequence.from(xs).mapM(f).fmap(ys => [y, ...ys])); 19 | } 20 | } 21 | }); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /benchmark/counter.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { counter } from '../src/counter'; 3 | 4 | describe('Benchmark:', function () { 5 | describe('Counter', function () { 6 | for (const radix of [10, 16, 32, 36]) { 7 | it(`native ${radix.toLocaleString('en')}`, function (done) { 8 | let i = 0; 9 | benchmark(`Counter native ${radix.toLocaleString('en')}`, () => 10 | (++i).toString(radix), done); 11 | }); 12 | 13 | it(`custom ${radix.toLocaleString('en')}`, function (done) { 14 | const count = counter(radix); 15 | benchmark(`Counter custom ${radix.toLocaleString('en')}`, () => 16 | count(), done); 17 | }); 18 | } 19 | 20 | it(`pad`, function (done) { 21 | const count = counter(10, '0'.repeat(16)); 22 | benchmark(`Counter pad`, () => count(), done); 23 | }); 24 | 25 | }); 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/scanl.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override scanl(f: (b: b, a: a) => b, z: b): Sequence, number]> { 6 | return new Sequence, number]>(([prev, iter, i] = [z, () => this.iterate(), 0]) => 7 | Sequence.Iterator.when, number]>>( 8 | iter(), 9 | () => 10 | i === 0 11 | ? Sequence.Data.cons(z) 12 | : Sequence.Data.cons(), 13 | thunk => 14 | Sequence.Data.cons, number]>( 15 | prev = f(prev, Sequence.Thunk.value(thunk)), 16 | [prev, Sequence.Thunk.iterator(thunk), Sequence.Thunk.index(thunk) + 1]))); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /benchmark/supervisor.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { Supervisor } from '../src/supervisor'; 3 | 4 | describe('Benchmark:', function () { 5 | describe('Supervisor', function () { 6 | it('new', function (done) { 7 | class SV extends Supervisor { 8 | } 9 | benchmark('Supervisor new', () => new SV().terminate(), done); 10 | }); 11 | 12 | it('cast', function (done) { 13 | const sv = new class extends Supervisor { }(); 14 | sv.register('', n => [n, undefined], undefined); 15 | benchmark('Supervisor cast', () => sv.cast('', 0), done); 16 | }); 17 | 18 | it('call', function (done) { 19 | const sv = new class extends Supervisor { }(); 20 | sv.register('', n => [n, undefined], undefined); 21 | benchmark('Supervisor call', done => sv.call('', 0, done), done, { defer: true }); 22 | }); 23 | 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/random.ts: -------------------------------------------------------------------------------- 1 | import { floor, random } from '../../../../alias'; 2 | import { Sequence } from '../../core'; 3 | import { compose } from '../../../../helper/compose'; 4 | 5 | compose(Sequence, class extends Sequence { 6 | public static override random(): Sequence>]> 7 | public static override random(gen: () => a): Sequence>]> 8 | public static override random(as: a[]): Sequence> 9 | public static override random(p: (() => number) | (() => a) | a[] = () => random()): Sequence>]> | Sequence> { 10 | return typeof p === 'function' 11 | ? Sequence.from(new Sequence((_, cons) => cons(p(), _))) 12 | : this.random().map(r => p[floor(r * p.length)]); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spica 2 | 3 | ![CI](https://github.com/falsandtru/spica/workflows/CI/badge.svg) 4 | 5 | Supervisor, Coroutine, Channel, select, assemble, AtomicPromise, Cancellation, Cache, List, Queue, Stack, and some utils. 6 | 7 | ## APIs 8 | 9 | - global 10 | - Supervisor 11 | - Coroutine, Coaggregator, Copropagator, Colistener, cofetch 12 | - Channel 13 | - select 14 | - Observer 15 | - AtomicPromise 16 | - Cancellation 17 | - Cache 18 | - List 19 | - HList 20 | - Heap 21 | - Queue 22 | - Stack 23 | - DataMap 24 | - MultiMap 25 | - AttrMap 26 | - Maybe, Just, Nothing 27 | - Either, Left, Right 28 | - Sequence 29 | - bundle, aggregate, assemble 30 | - curry 31 | - uncurry 32 | - flip 33 | - tuple 34 | - clock 35 | - throttle, debounce 36 | - uuid 37 | - random 38 | - counter 39 | - assign, clone, extend 40 | 41 | ## Browsers 42 | 43 | - Chrome 44 | - Firefox 45 | - Edge (Chromium edition only) 46 | 47 | ## Dependencies 48 | 49 | - unassert (in compiling source code) 50 | -------------------------------------------------------------------------------- /src/monad/applicative.ts: -------------------------------------------------------------------------------- 1 | import { Functor } from './functor'; 2 | import { curry } from '../curry'; 3 | 4 | export abstract class Applicative extends Functor { 5 | public abstract override fmap(f: (a: a) => b): Applicative; 6 | public abstract ap(this: Applicative<(a: a) => b>, a: Applicative): Applicative; 7 | public abstract bind(f: (a: a) => Applicative): Applicative; 8 | } 9 | export namespace Applicative { 10 | export declare function pure(a: a): Applicative; 11 | export function ap(af: Applicative<(a: a) => b>, aa: Applicative): Applicative; 12 | export function ap(af: Applicative<(a: a) => b>): (aa: Applicative) => Applicative; 13 | export function ap(af: Applicative<(a: a) => b>, aa?: Applicative): Applicative | ((aa: Applicative) => Applicative) { 14 | return aa 15 | ? af.bind(f => aa.fmap(curry(f))) 16 | : (aa: Applicative) => ap(af, aa); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /benchmark/maybe.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { Maybe, Just } from '../src/maybe'; 3 | 4 | describe('Benchmark:', function () { 5 | describe('Maybe', function () { 6 | it('Just', function (done) { 7 | benchmark('Maybe Just', () => Just(0), done); 8 | }); 9 | 10 | function bind(m: Maybe, n: number) { 11 | for (let i = 0; i < n; ++i) { 12 | m = m.bind(n => Just(n)); 13 | } 14 | m.extract(); 15 | } 16 | it('bind 1', function (done) { 17 | const maybe = Just(0); 18 | benchmark(`Maybe bind 1`, () => bind(maybe, 1), done); 19 | }); 20 | 21 | it('bind 10', function (done) { 22 | const maybe = Just(0); 23 | benchmark(`Maybe bind 10`, () => bind(maybe, 10), done); 24 | }); 25 | 26 | it('bind 100', function (done) { 27 | const maybe = Just(0); 28 | benchmark(`Maybe bind 100`, () => bind(maybe, 100), done); 29 | }); 30 | 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /src/alias.test.ts: -------------------------------------------------------------------------------- 1 | import { hasOwnProperty } from './alias'; 2 | 3 | describe('Unit: lib/alias', function () { 4 | describe('hasOwnProperty', function () { 5 | it('', () => { 6 | assert(hasOwnProperty(Object.assign({}, { 0: 0 }), '') === false); 7 | assert(hasOwnProperty(Object.assign({}, { 0: 0 }), '0')); 8 | assert(hasOwnProperty(Object.assign({}, { 0: 0 }), 'toString') === false); 9 | assert(hasOwnProperty(Object.assign(Object.create({}), { 0: 0 }), '') === false); 10 | assert(hasOwnProperty(Object.assign(Object.create({}), { 0: 0 }), '0')); 11 | assert(hasOwnProperty(Object.assign(Object.create({ 0: 0 }), {}), '0') === false); 12 | assert(hasOwnProperty(Object.assign(Object.create({ 0: 0 }), { 0: 0 }), '0')); 13 | assert(hasOwnProperty(Object.assign(Object.create(null), { 0: 0 }), '') === false); 14 | assert(hasOwnProperty(Object.assign(Object.create(null), { 0: 0 }), '0')); 15 | }); 16 | 17 | }); 18 | 19 | }); 20 | 21 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/segs.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | 3 | describe('Unit: lib/monad/sequence/member/segs', () => { 4 | describe('segs', () => { 5 | it('0', () => { 6 | assert.deepStrictEqual( 7 | Sequence.from([]) 8 | .segs() 9 | .extract(), 10 | [[]]); 11 | }); 12 | 13 | it('1', () => { 14 | assert.deepStrictEqual( 15 | Sequence.from([1]) 16 | .segs() 17 | .extract(), 18 | [[1], []]); 19 | }); 20 | 21 | it('2', () => { 22 | assert.deepStrictEqual( 23 | Sequence.from([1, 2]) 24 | .segs() 25 | .extract(), 26 | [[1], [1, 2], [2], []]); 27 | }); 28 | 29 | it('3', () => { 30 | assert.deepStrictEqual( 31 | Sequence.from([1, 2, 3]) 32 | .segs() 33 | .extract(), 34 | [[1], [1, 2], [1, 2, 3], [2], [2, 3], [3], []]); 35 | }); 36 | 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/concat.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override concat(as: Sequence, unknown>): Sequence>, Sequence.Iterator]> { 6 | return new Sequence>, Sequence.Iterator]>(([ai, bi] = [() => as.iterate(), Sequence.Iterator.done], cons) => 7 | Sequence.Iterator.when( 8 | ai(), 9 | () => cons(), 10 | (at, ar) => ( 11 | bi = bi === Sequence.Iterator.done 12 | ? () => Sequence.Thunk.value(at).iterate() 13 | : bi, 14 | Sequence.Iterator.when( 15 | bi(), 16 | () => (bi = Sequence.Iterator.done, ar()), 17 | bt => cons(Sequence.Thunk.value(bt), [() => at, Sequence.Thunk.iterator(bt)]))))); 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // Bug: ES2022 breaks the class init order. 4 | "target": "ES2021", 5 | "lib": [ 6 | "ES2022", 7 | "DOM", 8 | "DOM.Iterable" 9 | ], 10 | "outDir": "dist", 11 | "module": "ES2022", 12 | "moduleResolution": "bundler", 13 | "esModuleInterop": true, 14 | "sourceMap": true, 15 | "strict": true, 16 | "strictFunctionTypes": false, 17 | "noUnusedParameters": true, 18 | "noUnusedLocals": true, 19 | "noImplicitOverride": true, 20 | "noPropertyAccessFromIndexSignature": true, 21 | "suppressImplicitAnyIndexErrors": true, 22 | "ignoreDeprecations": "5.0", 23 | "noFallthroughCasesInSwitch": true, 24 | "noErrorTruncation": true, 25 | "forceConsistentCasingInFileNames": true 26 | }, 27 | "include": [ 28 | "*.ts", 29 | "src/**/*.ts", 30 | "test/**/*.ts", 31 | "benchmark/**/*.ts" 32 | ], 33 | "exclude": [ 34 | "node_modules" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /benchmark/either.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { Either, Right } from '../src/either'; 3 | 4 | describe('Benchmark:', function () { 5 | describe('Either', function () { 6 | it('Right', function (done) { 7 | benchmark('Either Right', () => Right(0), done); 8 | }); 9 | 10 | function bind(m: Either, n: number) { 11 | for (let i = 0; i < n; ++i) { 12 | m = m.bind(n => Right(n)); 13 | } 14 | m.extract(); 15 | } 16 | it('bind 1', function (done) { 17 | const either = Right(0); 18 | benchmark(`Either bind 1`, () => bind(either, 1), done); 19 | }); 20 | 21 | it('bind 10', function (done) { 22 | const either = Right(0); 23 | benchmark(`Either bind 10`, () => bind(either, 10), done); 24 | }); 25 | 26 | it('bind 100', function (done) { 27 | const either = Right(0); 28 | benchmark(`Either bind 100`, () => bind(either, 100), done); 29 | }); 30 | 31 | }); 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /benchmark/type.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { type } from '../src/type'; 3 | 4 | describe('Benchmark:', function () { 5 | describe('type', function () { 6 | it('undefined', function (done) { 7 | benchmark('type undefined', () => type(undefined), done); 8 | }); 9 | 10 | it('number', function (done) { 11 | benchmark('type number', () => type(0), done); 12 | }); 13 | 14 | it('function', function (done) { 15 | benchmark('type function', () => type(Function), done); 16 | }); 17 | 18 | it('array', function (done) { 19 | const obj = [] as object; 20 | benchmark('type array', () => type(obj), done); 21 | }); 22 | 23 | it('object', function (done) { 24 | const obj = {}; 25 | benchmark('type object', () => type(obj), done); 26 | }); 27 | 28 | it('named', function (done) { 29 | const obj = new Set(); 30 | benchmark('type named', () => type(obj), done); 31 | }); 32 | 33 | }); 34 | 35 | }); 36 | 37 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/map.test.ts: -------------------------------------------------------------------------------- 1 | import { nat } from '../../../sequence.test'; 2 | 3 | describe('Unit: lib/monad/sequence/member/map', () => { 4 | describe('map', () => { 5 | it('0', () => { 6 | assert.deepStrictEqual( 7 | nat 8 | .map(String) 9 | .take(0) 10 | .extract(), 11 | [].map(String)); 12 | }); 13 | 14 | it('1', () => { 15 | assert.deepStrictEqual( 16 | nat 17 | .map(String) 18 | .take(1) 19 | .extract(), 20 | [0].map(String)); 21 | }); 22 | 23 | it('2', () => { 24 | assert.deepStrictEqual( 25 | nat 26 | .map(String) 27 | .take(2) 28 | .extract(), 29 | [0, 1].map(String)); 30 | }); 31 | 32 | it('3', () => { 33 | assert.deepStrictEqual( 34 | nat 35 | .map(String) 36 | .take(3) 37 | .extract(), 38 | [0, 1, 2].map(String)); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/fmap.test.ts: -------------------------------------------------------------------------------- 1 | import { nat } from '../../../sequence.test'; 2 | 3 | describe('Unit: lib/monad/sequence/member/fmap', () => { 4 | describe('fmap', () => { 5 | it('0', () => { 6 | assert.deepStrictEqual( 7 | nat 8 | .fmap(String) 9 | .take(0) 10 | .extract(), 11 | [].map(String)); 12 | }); 13 | 14 | it('1', () => { 15 | assert.deepStrictEqual( 16 | nat 17 | .fmap(String) 18 | .take(1) 19 | .extract(), 20 | [0].map(String)); 21 | }); 22 | 23 | it('2', () => { 24 | assert.deepStrictEqual( 25 | nat 26 | .fmap(String) 27 | .take(2) 28 | .extract(), 29 | [0, 1].map(String)); 30 | }); 31 | 32 | it('3', () => { 33 | assert.deepStrictEqual( 34 | nat 35 | .fmap(String) 36 | .take(3) 37 | .extract(), 38 | [0, 1, 2].map(String)); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/filter.test.ts: -------------------------------------------------------------------------------- 1 | import { nat } from '../../../sequence.test'; 2 | 3 | describe('Unit: lib/monad/sequence/member/filter', () => { 4 | describe('filter', () => { 5 | it('0', () => { 6 | assert.deepStrictEqual( 7 | nat 8 | .filter(n => n % 2 === 0) 9 | .take(0) 10 | .extract(), 11 | []); 12 | }); 13 | 14 | it('1', () => { 15 | assert.deepStrictEqual( 16 | nat 17 | .filter(n => n % 2 === 0) 18 | .take(1) 19 | .extract(), 20 | [0]); 21 | }); 22 | 23 | it('2', () => { 24 | assert.deepStrictEqual( 25 | nat 26 | .filter(n => n % 2 === 0) 27 | .take(2) 28 | .extract(), 29 | [0, 2]); 30 | }); 31 | 32 | it('3', () => { 33 | assert.deepStrictEqual( 34 | nat 35 | .filter(n => n % 2 === 0) 36 | .take(3) 37 | .extract(), 38 | [0, 2, 4]); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/iterate.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override iterate(): Sequence.Thunk { 6 | return this.iterate_(); 7 | } 8 | private iterate_(z?: z, i = 0): Sequence.Thunk { 9 | const data = this.cons(z!, Sequence.Data.cons); 10 | switch (data.length) { 11 | case 0: 12 | return >[ 13 | undefined, 14 | Sequence.Iterator.done, 15 | -1 16 | ]; 17 | case 1: 18 | return >[ 19 | data[0], 20 | () => Sequence.Iterator.done(), 21 | i 22 | ]; 23 | case 2: 24 | return >[ 25 | data[0], 26 | () => this.iterate_(data[1] as z, i + 1), 27 | i 28 | ]; 29 | default: 30 | throw Sequence.Exception.invalidDataError(data); 31 | } 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/filterM.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override filterM(f: (a: a) => Sequence): Sequence>, Sequence.Iterator]> { 6 | return Sequence.from([0]) 7 | .bind(() => { 8 | const xs = this.extract(); 9 | switch (xs.length) { 10 | case 0: 11 | return Sequence.from([[]]); 12 | default: { 13 | const x = xs.shift()!; 14 | return f(x) 15 | .bind(b => 16 | b 17 | ? xs.length === 0 18 | ? Sequence.from([[x]]) 19 | : Sequence.from(xs).filterM(f).fmap(ys => [x, ...ys]) 20 | : xs.length === 0 21 | ? Sequence.from([[]]) 22 | : Sequence.from(xs).filterM(f)); 23 | } 24 | } 25 | }); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/permutations.test.ts: -------------------------------------------------------------------------------- 1 | import { nat } from '../../../sequence.test'; 2 | 3 | describe('Unit: lib/monad/sequence/member/permutations', () => { 4 | describe('permutations', () => { 5 | it('0', () => { 6 | assert.deepStrictEqual( 7 | nat 8 | .take(0) 9 | .permutations() 10 | .extract(), 11 | []); 12 | }); 13 | 14 | it('1', () => { 15 | assert.deepStrictEqual( 16 | nat 17 | .take(1) 18 | .permutations() 19 | .extract(), 20 | [[0]]); 21 | }); 22 | 23 | it('2', () => { 24 | assert.deepStrictEqual( 25 | nat 26 | .take(2) 27 | .permutations() 28 | .extract(), 29 | [[0, 1], [1, 0]]); 30 | }); 31 | 32 | it('3', () => { 33 | assert.deepStrictEqual( 34 | nat 35 | .take(3) 36 | .permutations() 37 | .extract(), 38 | [[0, 1, 2], [1, 0, 2], [2, 1, 0], [1, 2, 0], [2, 0, 1], [0, 2, 1]]); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/subsequences.test.ts: -------------------------------------------------------------------------------- 1 | import { nat } from '../../../sequence.test'; 2 | 3 | describe('Unit: lib/monad/sequence/member/subsequences', () => { 4 | describe('subsequences', () => { 5 | it('0', () => { 6 | assert.deepStrictEqual( 7 | nat 8 | .take(0) 9 | .subsequences() 10 | .extract(), 11 | [[]]); 12 | }); 13 | 14 | it('1', () => { 15 | assert.deepStrictEqual( 16 | nat 17 | .take(1) 18 | .subsequences() 19 | .extract(), 20 | [[], [0]]); 21 | }); 22 | 23 | it('2', () => { 24 | assert.deepStrictEqual( 25 | nat 26 | .take(2) 27 | .subsequences() 28 | .extract(), 29 | [[], [0], [1], [0, 1]]); 30 | }); 31 | 32 | it('3', () => { 33 | assert.deepStrictEqual( 34 | nat 35 | .take(3) 36 | .subsequences() 37 | .extract(), 38 | [[], [0], [1], [0, 1], [2], [0, 2], [1, 2], [0, 1, 2]]); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/intersect.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override intersect(a: Sequence, b: Sequence, cmp: (l: a, r: a) => number): Sequence, Sequence.Iterator]> { 6 | return new Sequence, Sequence.Iterator]>(([ai, bi] = [() => a.iterate(), () => b.iterate()], cons) => 7 | Sequence.Iterator.when( 8 | ai(), 9 | () => cons(), 10 | (at, ar) => 11 | Sequence.Iterator.when( 12 | bi(), 13 | () => cons(), 14 | (bt, br) => { 15 | const ord = cmp(Sequence.Thunk.value(at), Sequence.Thunk.value(bt)); 16 | if (ord < 0) return bi = () => bt, ar(); 17 | if (ord > 0) return br(); 18 | return cons(Sequence.Thunk.value(at), [Sequence.Thunk.iterator(at), Sequence.Thunk.iterator(bt)]); 19 | }))); 20 | } 21 | }); 22 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/mconcat.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override mconcat(as: Iterable>): Sequence, Sequence.Iterator]> { 6 | return [...as] 7 | .reduce, Sequence.Iterator]>>((a, b) => mconcat(a, b), Sequence.mempty); 8 | } 9 | }); 10 | 11 | function mconcat(a: Sequence, b: Sequence): Sequence, Sequence.Iterator]> { 12 | return new Sequence, Sequence.Iterator]>(([ai, bi] = [() => a.iterate(), () => b.iterate()], cons) => 13 | Sequence.Iterator.when( 14 | ai(), 15 | () => 16 | Sequence.Iterator.when( 17 | bi(), 18 | () => cons(), 19 | bt => cons(Sequence.Thunk.value(bt), [Sequence.Iterator.done, Sequence.Thunk.iterator(bt)])), 20 | at => cons(Sequence.Thunk.value(at), [Sequence.Thunk.iterator(at), bi]))); 21 | } 22 | -------------------------------------------------------------------------------- /src/uuid.test.ts: -------------------------------------------------------------------------------- 1 | import { uuid } from './uuid'; 2 | import { deviation } from './statistics'; 3 | 4 | describe('Unit: lib/uuid', () => { 5 | describe('v4', () => { 6 | it('type', () => { 7 | console.debug('lib/uuid', uuid()); 8 | console.debug('lib/uuid', uuid()); 9 | console.debug('lib/uuid', uuid()); 10 | assert(typeof uuid() === 'string'); 11 | }); 12 | 13 | it('format', () => { 14 | assert(/^[0-9a-f-]+$/.test(uuid())); 15 | }); 16 | 17 | it('size', () => { 18 | assert(uuid().length === 36); 19 | }); 20 | 21 | it('inequality', () => { 22 | assert(uuid() !== uuid()); 23 | }); 24 | 25 | it('deviation', () => { 26 | const str = [...Array(1e3)].map(() => uuid().replace(/^(\w+)-(\w+)-.(\w+)-.(\w+)-(\w+)$/, '$1$2$3$4$5')).join(''); 27 | assert(/^[0-9a-f]+$/.test(str)); 28 | const dist = [...str].reduce((d, c) => (++d[parseInt(c, 16)], d), Array(16).fill(0)); 29 | console.debug('lib/uuid deviation', deviation(dist) / (str.length / dist.length), dist); 30 | assert(deviation(dist) / (str.length / dist.length) < 0.04); 31 | }); 32 | 33 | }); 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/iterate.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | 3 | describe('Unit: lib/monad/sequence/member/iterate', () => { 4 | describe('iterate', () => { 5 | it('', () => { 6 | let thunk = new Sequence((_ = 0, cons) => cons()).iterate(); 7 | assert.deepStrictEqual(thunk, [undefined, Sequence.Iterator.done, -1]); 8 | 9 | thunk = new Sequence((n = 0, cons) => cons(n)).iterate(); 10 | assert.deepStrictEqual(thunk, [0, Sequence.Thunk.iterator(thunk), 0]); 11 | 12 | thunk = new Sequence((n = 1, cons) => n < 4 ? cons(n, n * 2) : cons(n)).iterate(); 13 | assert.deepStrictEqual(thunk, [1, Sequence.Thunk.iterator(thunk), 0]); 14 | thunk = Sequence.Thunk.iterator(thunk)(); 15 | assert.deepStrictEqual(thunk, [2, Sequence.Thunk.iterator(thunk), 1]); 16 | thunk = Sequence.Thunk.iterator(thunk)(); 17 | assert.deepStrictEqual(thunk, [4, Sequence.Thunk.iterator(thunk), 2]); 18 | thunk = Sequence.Thunk.iterator(thunk)(); 19 | assert.deepStrictEqual(thunk, [undefined, Sequence.Iterator.done, -1]); 20 | }); 21 | 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /benchmark/map.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { DataMap } from '../src/datamap'; 3 | import { AttrMap } from '../src/attrmap'; 4 | 5 | describe('Benchmark:', function () { 6 | describe('DataMap', function () { 7 | it('get', function (done) { 8 | const map = new DataMap(); 9 | const key = ['a', 'b']; 10 | map.set(key, 0); 11 | benchmark('DataMap get', () => map.get(key), done); 12 | }); 13 | 14 | it('set', function (done) { 15 | const map = new DataMap(); 16 | const key = ['a', 'b']; 17 | benchmark('DataMap set', () => map.set(key, 0), done); 18 | }); 19 | 20 | }); 21 | 22 | describe('AttrMap', function () { 23 | it('get', function (done) { 24 | const map = new AttrMap<{}, string, number>(); 25 | const obj = {}; 26 | map.set(obj, 'abc', 0); 27 | benchmark('AttrMap get', () => map.get(obj, 'abc'), done); 28 | }); 29 | 30 | it('set', function (done) { 31 | const map = new AttrMap<{}, string, number>(); 32 | const obj = {}; 33 | benchmark('AttrMap set', () => map.set(obj, 'abc', 0), done); 34 | }); 35 | 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/group.test.ts: -------------------------------------------------------------------------------- 1 | import { nat } from '../../../sequence.test'; 2 | 3 | describe('Unit: lib/monad/sequence/member/group', () => { 4 | describe('group', () => { 5 | it('empty', () => { 6 | assert.deepStrictEqual( 7 | nat 8 | .take(0) 9 | .group(() => true) 10 | .extract(), 11 | []); 12 | assert.deepStrictEqual( 13 | nat 14 | .take(0) 15 | .group(() => false) 16 | .extract(), 17 | []); 18 | }); 19 | 20 | it('false', () => { 21 | assert.deepStrictEqual( 22 | nat 23 | .take(3) 24 | .group(() => false) 25 | .extract(), 26 | [[0], [1], [2]]); 27 | }); 28 | 29 | it('true', () => { 30 | assert.deepStrictEqual( 31 | nat 32 | .take(3) 33 | .group(() => true) 34 | .extract(), 35 | [[0, 1, 2]]); 36 | }); 37 | 38 | it('mod3', () => { 39 | assert.deepStrictEqual( 40 | nat 41 | .take(5) 42 | .group((_, n) => n % 3 > 0) 43 | .extract(), 44 | [[0, 1, 2], [3, 4]]); 45 | }); 46 | 47 | }); 48 | 49 | }); 50 | -------------------------------------------------------------------------------- /src/cofetch.test.ts: -------------------------------------------------------------------------------- 1 | import { cofetch } from './cofetch'; 2 | import { Cache } from './cache'; 3 | 4 | describe('Unit: lib/cofetch', () => { 5 | describe('cofetch', () => { 6 | it('basic', async () => { 7 | const co = cofetch(''); 8 | const types = []; 9 | for await (const ev of co) { 10 | assert(ev instanceof ProgressEvent); 11 | assert(['loadstart', 'progress', 'loadend'].includes(ev.type)); 12 | types.push(ev.type); 13 | } 14 | assert.deepStrictEqual([types[0], types.at(-1)], ['loadstart', 'loadend']); 15 | assert.deepStrictEqual(types.slice(1, -1), Array(types.length - 2).fill('progress')); 16 | assert(await co instanceof XMLHttpRequest); 17 | }); 18 | 19 | it('cancel', async () => { 20 | const co = cofetch(''); 21 | const types = new Set(); 22 | for await (const ev of co) { 23 | types.add(ev.type); 24 | co.cancel(); 25 | } 26 | assert.deepStrictEqual([...types], ['loadstart', 'loadend']); 27 | assert(await co instanceof XMLHttpRequest); 28 | }); 29 | 30 | it('cache', async () => { 31 | cofetch('', { cache: new Cache(9) }); 32 | }); 33 | 34 | }); 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /src/memoize.test.ts: -------------------------------------------------------------------------------- 1 | import { memoize, reduce } from './memoize'; 2 | import { Cache } from './cache'; 3 | 4 | describe('Unit: lib/memoize', () => { 5 | describe('memoize', () => { 6 | it('Map', () => { 7 | let cnt = 0; 8 | const f = memoize(key => key + ++cnt); 9 | assert(f(0) === 1); 10 | assert(f(0) === 1); 11 | }); 12 | 13 | it('Cache', () => { 14 | let cnt = 0; 15 | const f = memoize(key => key + ++cnt, new Cache(9)); 16 | assert(f(0) === 1); 17 | assert(f(0) === 1); 18 | }); 19 | 20 | it('Array', () => { 21 | let cnt = 0; 22 | const f = memoize(key => key + ++cnt, []); 23 | assert(f(0) === 1); 24 | assert(f(0) === 1); 25 | }); 26 | 27 | it('Object', () => { 28 | let cnt = 0; 29 | const f = memoize(key => key + ++cnt, {}); 30 | assert(f(0) === 1); 31 | assert(f(0) === 1); 32 | }); 33 | }); 34 | 35 | describe('reduce', () => { 36 | it('', () => { 37 | let cnt = 0; 38 | const f = reduce(key => key + ++cnt); 39 | assert(f(0) === 1); 40 | assert(f(0) === 1); 41 | }); 42 | }); 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /benchmark/inline.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | 3 | describe('Benchmark:', function () { 4 | describe('Inline', function () { 5 | for (const size of [1, 1e1, 1e2]) { 6 | const arr = Array(size).fill(1); 7 | 8 | it(`for ${size.toLocaleString('en')}`, function (done) { 9 | benchmark(`for ${size.toLocaleString('en')}`, () => { 10 | let acc = 0; 11 | for (let i = 0; i < size; ++i) { 12 | acc += arr[i]; 13 | } 14 | acc; 15 | }, done); 16 | }); 17 | 18 | it(`Function ${size.toLocaleString('en')}`, function (done) { 19 | const sum = Function('arr', [ 20 | '"use strict";', 21 | 'return ', 22 | arr.reduce((acc, _, i) => acc + `+ arr[${i}]`, '').slice(1), 23 | ].join('')); 24 | benchmark(`Inline Function ${size.toLocaleString('en')}`, () => sum(arr), done); 25 | }); 26 | 27 | it(`eval ${size.toLocaleString('en')}`, function (done) { 28 | const sum = eval([ 29 | '() =>', 30 | arr.reduce((acc, _, i) => acc + `+ arr[${i}]`, '').slice(1), 31 | ].join('')); 32 | benchmark(`Inline eval ${size.toLocaleString('en')}`, () => sum(), done); 33 | }); 34 | } 35 | }); 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/from.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { counter } from '../../../../counter'; 3 | 4 | describe('Unit: lib/monad/sequence/member/static/from', () => { 5 | describe('Sequence.from', () => { 6 | it('side effect', () => { 7 | const s = Sequence.from(Sequence.random(counter())).take(5); 8 | assert(s.extract().length === 5); 9 | assert.notDeepStrictEqual( 10 | s.extract(), 11 | s.extract()); 12 | }); 13 | 14 | it('idempotence', () => { 15 | assert.deepStrictEqual( 16 | Sequence.from([0, 1]) 17 | .foldr((a, b) => Sequence.from([a].concat(b.extract()).concat(b.extract())), Sequence.from([])) 18 | .extract(), 19 | [0, 1, 1]); 20 | }); 21 | 22 | it('array', () => { 23 | assert.deepStrictEqual( 24 | Sequence.from([]) 25 | .extract(), 26 | []); 27 | assert.deepStrictEqual( 28 | Sequence.from([0]) 29 | .extract(), 30 | [0]); 31 | assert.deepStrictEqual( 32 | Sequence.from([0, 1]) 33 | .extract(), 34 | [0, 1]); 35 | assert.deepStrictEqual( 36 | Sequence.from([0, 1, 2]) 37 | .extract(), 38 | [0, 1, 2]); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /src/copropagator.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from './alias'; 2 | import { Coroutine, CoroutineOptions } from './coroutine'; 3 | import { AtomicPromise, never } from './promise'; 4 | 5 | export class Copropagator extends Coroutine { 6 | constructor( 7 | coroutines: Iterable>, 8 | reducer: (results: T[]) => T = results => results[0], 9 | opts?: CoroutineOptions, 10 | ) { 11 | const cs = isArray(coroutines) 12 | ? coroutines 13 | : [...coroutines]; 14 | super(async function* () { 15 | this.then( 16 | result => { 17 | for (const co of cs) { 18 | co[Coroutine.exit](result); 19 | } 20 | }, 21 | reason => { 22 | const rejection = AtomicPromise.reject(reason); 23 | for (const co of cs) { 24 | co[Coroutine.exit](rejection); 25 | } 26 | }); 27 | AtomicPromise.all(cs).then( 28 | results => 29 | results.length === 0 30 | ? void this[Coroutine.terminate](new Error(`Spica: Copropagator: No result`)) 31 | : void this[Coroutine.exit](reducer(results)), 32 | reason => 33 | void this[Coroutine.terminate](reason)); 34 | return never; 35 | }, { delay: false, ...opts }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/cycle.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { counter } from '../../../../counter'; 3 | 4 | describe('Unit: lib/monad/sequence/member/static/cycle', () => { 5 | describe('Sequence.cycle', () => { 6 | it('side effect', () => { 7 | const s = Sequence.cycle(Sequence.random(counter()).take(2)).take(5); 8 | assert(s.extract().length === 5); 9 | assert.notDeepStrictEqual( 10 | s.extract(), 11 | s.extract()); 12 | }); 13 | 14 | it('idempotence', () => { 15 | assert.deepStrictEqual( 16 | Sequence.cycle([1]) 17 | .take(2) 18 | .foldr((a, b) => Sequence.from([a].concat(b.extract()).concat(b.extract())), Sequence.from([0])) 19 | .extract(), 20 | [1, 1, 0, 0, 1, 0, 0]); 21 | }); 22 | 23 | it('array', () => { 24 | assert.deepStrictEqual( 25 | Sequence.cycle([0]) 26 | .take(3) 27 | .extract(), 28 | [0, 0, 0]); 29 | assert.deepStrictEqual( 30 | Sequence.cycle([0, 1]) 31 | .take(3) 32 | .extract(), 33 | [0, 1, 0]); 34 | assert.deepStrictEqual( 35 | Sequence.cycle([0, 1, 2]) 36 | .take(3) 37 | .extract(), 38 | [0, 1, 2]); 39 | }); 40 | 41 | }); 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /benchmark/coroutine.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { Coroutine } from '../src/coroutine'; 3 | import { AtomicPromise } from '../src/promise'; 4 | 5 | describe('Benchmark:', function () { 6 | afterEach(() => new AtomicPromise(requestAnimationFrame)); 7 | 8 | describe('Asyncgenerator', function () { 9 | it('iterate ', function (done) { 10 | const iter = async function* () { 11 | while (true) yield; 12 | }()[Symbol.asyncIterator](); 13 | benchmark('Asyncgenerator iterate', done => void iter.next().then(done), done, { defer: true }); 14 | }); 15 | 16 | }); 17 | 18 | describe('Coroutine', function () { 19 | it('new', function (done) { 20 | benchmark('Coroutine new', () => void new Coroutine(async function* () { }, { capacity: 0, run: false }), done); 21 | }); 22 | 23 | it('run', function (done) { 24 | benchmark('Coroutine run', done => void new Coroutine(async function* () { }, { capacity: 0, delay: false }).then(done), done, { defer: true }); 25 | }); 26 | 27 | it('ask', function (done) { 28 | const port = new Coroutine(async function* () { 29 | while (true) yield; 30 | }, { capacity: 0, delay: false })[Coroutine.port]; 31 | benchmark('Coroutine ask', done => void port.ask(0).then(done), done, { defer: true }); 32 | }); 33 | 34 | }); 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function (config) { 2 | config.set({ 3 | browsers: ['Chrome', 'Firefox'], 4 | customLaunchers: { 5 | Chrome_bench: { 6 | base: 'Chrome', 7 | flags: ['--js-flags="--expose-gc"'], 8 | }, 9 | }, 10 | frameworks: ['mocha', 'power-assert'], 11 | files: [ 12 | { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.js', watched: false, served: false, included: true, integrity: 'sha512-2iwCHjuj+PmdCyvb88rMOch0UcKQxVHi/gsAml1fN3eg82IDaO/cdzzeXX4iF2VzIIes7pODE1/G0ts3QBwslA==' }, 13 | { pattern: 'https://cdnjs.cloudflare.com/ajax/libs/benchmark/2.1.4/benchmark.js', watched: false, served: false, included: true, integrity: 'sha512-XnVGk21Ij51MbU8XezQpkwZ1/GA8b5qmoVGIOdJLBYycutjkaeemipzRJP7P6mEJl99OfnweA7M3e4WLfuG7Aw==' }, 14 | { pattern: 'dist/**/*.{js,map}', watched: true, served: true, included: true }, 15 | ], 16 | reporters: ['dots'], 17 | preprocessors: { 18 | 'dist/**/*.js': ['coverage'], 19 | }, 20 | coverageReporter: { 21 | dir: 'coverage', 22 | reporters: [ 23 | { type: 'html', subdir: browser => browser.split(/\s/)[0] }, 24 | { type: 'text-summary', subdir: '.', file: 'summary.txt' }, 25 | ], 26 | }, 27 | browserDisconnectTimeout: 60 * 1e3, 28 | browserNoActivityTimeout: 300 * 1e3, 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /src/dict/attrmap.ts: -------------------------------------------------------------------------------- 1 | import { Dict } from '../dict'; 2 | 3 | export class AttrMap { 4 | constructor( 5 | entries?: Iterable, 6 | private readonly KeyMap: new (entries?: Iterable | null) => Dict = WeakMap, 7 | private readonly ValueMap: new (entries?: Iterable | null) => Dict = Map 8 | ) { 9 | if (entries) for (const { 0: c, 1: k, 2: v } of entries) { 10 | this.set(c, k, v); 11 | } 12 | } 13 | private readonly memory = new this.KeyMap>(); 14 | public get(ctx: C, key: K): V | undefined { 15 | return this.memory.get(ctx)?.get(key); 16 | } 17 | public set(ctx: C, key: K, value: V): this { 18 | this.memory.get(ctx)?.set(key, value) || this.memory.set(ctx, new this.ValueMap([[key, value]])); 19 | return this; 20 | } 21 | public has(ctx: C): boolean 22 | public has(ctx: C, key: K): boolean 23 | public has(ctx: C, key?: K): boolean { 24 | return arguments.length < 2 25 | ? this.memory.has(ctx) 26 | : this.memory.get(ctx)?.has(key!) ?? false; 27 | } 28 | public delete(ctx: C): boolean 29 | public delete(ctx: C, key: K): boolean 30 | public delete(ctx: C, key?: K): boolean { 31 | return arguments.length < 2 32 | ? this.memory.delete(ctx) 33 | : this.memory.get(ctx)?.delete(key!) ?? false; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/list/clist.test.ts: -------------------------------------------------------------------------------- 1 | import { List } from './clist'; 2 | 3 | describe('Unit: lib/clist', () => { 4 | describe('List', () => { 5 | it('', () => { 6 | class Node { 7 | constructor(public value: T) { 8 | } 9 | public next?: this; 10 | public prev?: this; 11 | } 12 | const list = new List>(); 13 | 14 | assert(list.length === 0); 15 | assert(list.shift() === undefined); 16 | assert(list.pop() === undefined); 17 | assert(list.length === 0); 18 | assert(list.head === undefined); 19 | assert(list.length === 0); 20 | 21 | list.unshift(new Node(1)); 22 | assert(list.length === 1); 23 | assert(list.shift()?.value === 1); 24 | assert(list.length === 0); 25 | assert(list.shift() === undefined); 26 | assert(list.length === 0); 27 | 28 | list.push(new Node(1)); 29 | assert(list.length === 1); 30 | assert(list.pop()?.value === 1); 31 | assert(list.length === 0); 32 | assert(list.pop() === undefined); 33 | assert(list.length === 0); 34 | 35 | list.push(new Node(1)); 36 | list.unshift(new Node(0)); 37 | list.push(new Node(2)); 38 | assert(list.length === 3); 39 | assert(list.shift()?.value === 0); 40 | assert(list.pop()?.value === 2); 41 | assert(list.length === 1); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/list/list.test.ts: -------------------------------------------------------------------------------- 1 | import { List } from './list'; 2 | 3 | describe('Unit: lib/list', () => { 4 | describe('List', () => { 5 | it('', () => { 6 | class Node { 7 | constructor(public value: T) { 8 | } 9 | public next?: this; 10 | public prev?: this; 11 | } 12 | const list = new List>(); 13 | 14 | assert(list.length === 0); 15 | assert(list.shift() === undefined); 16 | assert(list.pop() === undefined); 17 | assert(list.length === 0); 18 | assert(list.head === undefined); 19 | assert(list.length === 0); 20 | 21 | list.unshift(new Node(1)); 22 | assert(list.length === 1); 23 | assert(list.shift()?.value === 1); 24 | assert(list.length === 0); 25 | assert(list.shift() === undefined); 26 | assert(list.length === 0); 27 | 28 | list.push(new Node(1)); 29 | assert(list.length === 1); 30 | assert(list.pop()?.value === 1); 31 | assert(list.length === 0); 32 | assert(list.pop() === undefined); 33 | assert(list.length === 0); 34 | 35 | list.push(new Node(1)); 36 | list.unshift(new Node(0)); 37 | list.push(new Node(2)); 38 | assert(list.length === 3); 39 | assert(list.shift()?.value === 0); 40 | assert(list.pop()?.value === 2); 41 | assert(list.length === 1); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/uuid.ts: -------------------------------------------------------------------------------- 1 | // UUID Version 4 2 | 3 | // 32: Closest power of 2 from the number of random values in a UUID. 4 | // 4: Number of bits required to represent a hex number. 5 | // 8: Number of 16 bit values required to create a UUID. 6 | 7 | const digit = 16; 8 | const unit = 32 / (digit / 4) as 8; 9 | const buffer = new Uint16Array(unit * 64); 10 | assert(buffer.length === 512); 11 | const HEX = [...Array(16)].map((_, i) => i.toString(16)).join(''); 12 | assert(HEX.length === 16); 13 | let index = 0; 14 | 15 | export function uuid(): string { 16 | if (index === 0) { 17 | crypto.getRandomValues(buffer); 18 | index = buffer.length; 19 | } 20 | index -= unit; 21 | return gen(); 22 | } 23 | 24 | assert(eval('(function () {return this})()') === undefined); 25 | const gen = ((i, offset) => eval([ 26 | '() => {', 27 | ...[...Array(unit)].map((_, i) => 28 | `const buf${i} = buffer[index + ${i}];`), 29 | 'return', 30 | 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/./g, c => { 31 | assert(i < 32); 32 | assert(offset >= 0); 33 | offset ||= digit; 34 | switch (c) { 35 | case 'x': 36 | return `+ HEX[buf${i++ >> 2} >> ${offset -= 4} & 0x0f]`; 37 | case 'y': 38 | return `+ HEX[buf${i++ >> 2} >> ${offset -= 4} & 0x03 | 0x08]`; 39 | default: 40 | return `+ '${c}'`; 41 | } 42 | }).slice(1), 43 | '}', 44 | ].join('')))(0, 0); 45 | -------------------------------------------------------------------------------- /src/chrono.ts: -------------------------------------------------------------------------------- 1 | import { Queue } from './queue'; 2 | import { causeAsyncException } from './exception'; 3 | 4 | let time: number | undefined; 5 | let count = 0; 6 | export function now(nocache?: boolean): number { 7 | if (time === undefined) { 8 | clock.now(() => time = undefined); 9 | } 10 | else if (!nocache && count++ !== 20) { 11 | return time; 12 | } 13 | count = 1; 14 | return time = Date.now(); 15 | } 16 | 17 | export const clock = new class Clock extends Promise { 18 | constructor() { 19 | super(resolve => resolve(undefined)); 20 | // Promise subclass is slow. 21 | const clock = Promise.resolve() as Clock; 22 | clock.next = this.next; 23 | clock.now = this.now; 24 | return clock; 25 | } 26 | public next(callback: () => void): void { 27 | scheduled || schedule(); 28 | clock.then(callback); 29 | } 30 | public now(callback: () => void): void { 31 | scheduled || schedule(); 32 | queue.push(callback); 33 | } 34 | }; 35 | 36 | const queue = new Queue<() => void>(); 37 | let scheduled = false; 38 | 39 | function schedule(): void { 40 | scheduled = true; 41 | clock.then(run); 42 | } 43 | 44 | function run(): void { 45 | for (let cb: () => void; cb = queue.pop()!;) { 46 | try { 47 | cb(); 48 | } 49 | catch (reason) { 50 | causeAsyncException(reason); 51 | } 52 | } 53 | scheduled = false; 54 | } 55 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/foldr.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { nat } from '../../../sequence.test'; 3 | 4 | describe('Unit: lib/monad/sequence/member/foldr', () => { 5 | describe('foldr', () => { 6 | it('0', () => { 7 | assert.deepStrictEqual( 8 | nat 9 | .drop(1) 10 | .foldr((a, b) => Sequence.mappend(Sequence.from([a]), b), Sequence.from([0])) 11 | .take(0) 12 | .extract(), 13 | []); 14 | }); 15 | 16 | it('1', () => { 17 | assert.deepStrictEqual( 18 | nat 19 | .drop(1) 20 | .foldr((a, b) => Sequence.mappend(Sequence.from([a]), b), Sequence.from([0])) 21 | .take(1) 22 | .extract(), 23 | [1]); 24 | }); 25 | 26 | it('2', () => { 27 | assert.deepStrictEqual( 28 | nat 29 | .drop(1) 30 | .foldr((a, b) => Sequence.mappend(Sequence.from([a]), b), Sequence.from([0])) 31 | .take(2) 32 | .extract(), 33 | [1, 2]); 34 | }); 35 | 36 | it('1..2', () => { 37 | assert.deepStrictEqual( 38 | Sequence.from([1, 2]) 39 | .foldr((a, b) => Sequence.mappend(Sequence.from([a, b.take(1).extract()[0]]), b), Sequence.from([0])) 40 | .extract(), 41 | [1, 2, 2, 0, 0]); 42 | }); 43 | 44 | }); 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /src/future.test.ts: -------------------------------------------------------------------------------- 1 | import { Future, AtomicFuture } from './future'; 2 | 3 | describe('Unit: lib/future', () => { 4 | describe('Future', () => { 5 | it('strict', async () => { 6 | const data = new Future(); 7 | assert(data instanceof Future); 8 | assert(data.then() instanceof Future === false); 9 | assert(await data.bind(0) === 0); 10 | assert.throws(() => data.bind(1)); 11 | }); 12 | 13 | it('loose', async () => { 14 | const data = new Future(false); 15 | assert(data instanceof Future); 16 | assert(data.then() instanceof Future === false); 17 | assert(await data.bind(0) === 0); 18 | assert(await data.bind(1) === 0); 19 | }); 20 | 21 | }); 22 | 23 | describe('AtomicFuture', () => { 24 | it('strict', async () => { 25 | const data = new AtomicFuture(); 26 | assert(data instanceof AtomicFuture); 27 | assert(data.then() instanceof AtomicFuture === false); 28 | assert(await data.bind(0) === 0); 29 | assert.throws(() => data.bind(1)); 30 | }); 31 | 32 | it('loose', async () => { 33 | const data = new AtomicFuture(false); 34 | assert(data instanceof AtomicFuture); 35 | assert(data.then() instanceof AtomicFuture === false); 36 | assert(await data.bind(0) === 0); 37 | assert(await data.bind(1) === 0); 38 | }); 39 | 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/difference.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override difference(a: Sequence, b: Sequence, cmp: (a: a, b: a) => number): Sequence, Sequence.Iterator]> { 6 | return new Sequence, Sequence.Iterator]>(([ai, bi] = [() => a.iterate(), () => b.iterate()], cons) => 7 | Sequence.Iterator.when( 8 | ai(), 9 | () => 10 | Sequence.Iterator.when( 11 | bi(), 12 | () => cons(), 13 | bt => cons(Sequence.Thunk.value(bt), [Sequence.Iterator.done, Sequence.Thunk.iterator(bt)])), 14 | (at, ar) => 15 | Sequence.Iterator.when( 16 | bi(), 17 | () => cons(Sequence.Thunk.value(at), [Sequence.Thunk.iterator(at), Sequence.Iterator.done]), 18 | bt => { 19 | const ord = cmp(Sequence.Thunk.value(at), Sequence.Thunk.value(bt)); 20 | if (ord < 0) return cons(Sequence.Thunk.value(at), [Sequence.Thunk.iterator(at), () => bt]); 21 | if (ord > 0) return cons(Sequence.Thunk.value(bt), [() => at, Sequence.Thunk.iterator(bt)]); 22 | return bi = () => Sequence.Thunk.iterator(bt)(), ar(); 23 | }))); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/ap.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override ap(this: Sequence<(a: a) => z, unknown>, a: Sequence): Sequence>, Sequence.Iterator]> 6 | public override ap(this: Sequence<(a: a, b: b) => z, unknown>, a: Sequence): Sequence<(b: b) => z, [Sequence.Iterator>, Sequence.Iterator]> 7 | public override ap(this: Sequence<(a: a, b: b, c: c) => z, unknown>, a: Sequence): Sequence<(b: b, c: c) => z, [Sequence.Iterator>, Sequence.Iterator]> 8 | public override ap(this: Sequence<(a: a, b: b, c: c, d: d) => z, unknown>, a: Sequence): Sequence<(b: b, c: c, d: d) => z, [Sequence.Iterator>, Sequence.Iterator]> 9 | public override ap(this: Sequence<(a: a, b: b, c: c, d: d, e: e) => z, unknown>, a: Sequence): Sequence<(b: b, c: c, d: d, e: e) => z, [Sequence.Iterator>, Sequence.Iterator]> 10 | public override ap(this: Sequence<(...as: unknown[]) => z, unknown>, a: Sequence): Sequence>, Sequence.Iterator]> { 11 | return Sequence.ap(this, a); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /src/counter.ts: -------------------------------------------------------------------------------- 1 | const dict = [ 2 | ...[...Array(10)].map((_, i) => i), 3 | ...[...Array(26)].map((_, i) => String.fromCharCode(0x61 + i)), 4 | ...[...Array(26)].map((_, i) => String.fromCharCode(0x41 + i)), 5 | ].join(''); 6 | assert(dict.length === 62); 7 | assert(dict[0] === '0'); 8 | assert(dict.at(-1) === 'Z'); 9 | 10 | export function counter(radix: number = 10, pad: string = '', numbers: string = dict): () => string { 11 | assert(radix <= numbers.length); 12 | return format(pad, counter$(radix, numbers)); 13 | } 14 | 15 | function counter$(radix: number, numbers: string): () => string { 16 | let cnt = 0; 17 | if (radix === 10 && numbers.slice(0, 10) === dict.slice(0, 10)) return () => `${++cnt}`; 18 | let carry: () => string; 19 | let str = ''; 20 | return (): string => { 21 | cnt = ++cnt % radix; 22 | if (cnt !== 0) return `${str}${numbers[cnt]}`; 23 | carry ??= counter$(radix, numbers); 24 | str = carry(); 25 | return `${str}${numbers[cnt]}`; 26 | }; 27 | } 28 | 29 | function format(pad: string, counter: () => string): () => string { 30 | if (pad === '') return counter; 31 | let len = 0; 32 | let str = ''; 33 | return () => { 34 | const count = counter(); 35 | assert(count.length > 0); 36 | if (count.length === len) return `${str}${count}`; 37 | len = count.length; 38 | str = pad.length > len ? pad.slice(0, pad.length - len) : ''; 39 | return `${str}${count}`; 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/union.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public static override union(a: Sequence, b: Sequence, cmp: (a: a, b: a) => number): Sequence, Sequence.Iterator]> { 6 | return new Sequence, Sequence.Iterator]>(([ai, bi] = [() => a.iterate(), () => b.iterate()], cons) => 7 | Sequence.Iterator.when( 8 | ai(), 9 | () => 10 | Sequence.Iterator.when( 11 | bi(), 12 | () => cons(), 13 | bt => cons(Sequence.Thunk.value(bt), [Sequence.Iterator.done, Sequence.Thunk.iterator(bt)])), 14 | at => 15 | Sequence.Iterator.when( 16 | bi(), 17 | () => cons(Sequence.Thunk.value(at), [Sequence.Thunk.iterator(at), Sequence.Iterator.done]), 18 | bt => { 19 | const ord = cmp(Sequence.Thunk.value(at), Sequence.Thunk.value(bt)); 20 | if (ord < 0) return cons(Sequence.Thunk.value(at), [Sequence.Thunk.iterator(at), () => bt]); 21 | if (ord > 0) return cons(Sequence.Thunk.value(bt), [() => at, Sequence.Thunk.iterator(bt)]); 22 | return cons(Sequence.Thunk.value(at), [Sequence.Thunk.iterator(at), Sequence.Thunk.iterator(bt)]); 23 | }))); 24 | } 25 | }); 26 | -------------------------------------------------------------------------------- /src/colistener.ts: -------------------------------------------------------------------------------- 1 | import { AtomicFuture } from './future'; 2 | import { Coroutine, CoroutineOptions } from './coroutine'; 3 | import { Queue } from './queue'; 4 | 5 | export class Colistener extends Coroutine { 6 | constructor( 7 | listen: (this: Colistener, listener: (value: T) => void) => () => void, 8 | opts: CoroutineOptions = {}, 9 | ) { 10 | super(async function* (this: Colistener) { 11 | const queue = new Queue; 12 | let notifier: AtomicFuture = new AtomicFuture(); 13 | let notifiable: boolean = true; 14 | this.finally(listen.call(this, (value: T) => { 15 | assert(this[Coroutine.alive]); 16 | if (notifiable) { 17 | notifier.bind(); 18 | notifiable = false; 19 | } 20 | queue.push(value); 21 | while (queue.length > (opts.capacity || 1)) { 22 | queue.pop()!; 23 | } 24 | assert(queue.length > 0); 25 | })); 26 | while (true) { 27 | await notifier; 28 | notifier = new AtomicFuture(); 29 | notifiable = true; 30 | while (!queue.isEmpty()) { 31 | yield queue.pop()!; 32 | } 33 | } 34 | }, { ...opts, capacity: -1, run: false }); 35 | this[Coroutine.init](); 36 | } 37 | public close(this: Colistener, value?: U): void; 38 | public close(value: U): void; 39 | public close(value: U): void { 40 | this[Coroutine.exit](value); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/scanl.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | 3 | describe('Unit: lib/monad/sequence/member/scanl', () => { 4 | describe('scanl', () => { 5 | it('empty', () => { 6 | assert.deepStrictEqual( 7 | Sequence.from([]) 8 | .scanl((a, b) => a + b, '') 9 | .extract(), 10 | ['']); 11 | }); 12 | 13 | it('0', () => { 14 | assert.deepStrictEqual( 15 | Sequence.from('abc') 16 | .scanl((a, b) => a + b, '') 17 | .take(0) 18 | .extract(), 19 | []); 20 | }); 21 | 22 | it('1', () => { 23 | assert.deepStrictEqual( 24 | Sequence.from('abc') 25 | .scanl((a, b) => a + b, '') 26 | .take(1) 27 | .extract(), 28 | ['a']); 29 | }); 30 | 31 | it('2', () => { 32 | assert.deepStrictEqual( 33 | Sequence.from('abc') 34 | .scanl((a, b) => a + b, '') 35 | .take(2) 36 | .extract(), 37 | ['a', 'ab']); 38 | }); 39 | 40 | it('3', () => { 41 | assert.deepStrictEqual( 42 | Sequence.from('abc') 43 | .scanl((a, b) => a + b, '') 44 | .take(3) 45 | .extract(), 46 | ['a', 'ab', 'abc']); 47 | }); 48 | 49 | it('4', () => { 50 | assert.deepStrictEqual( 51 | Sequence.from('abc') 52 | .scanl((a, b) => a + b, '') 53 | .take(4) 54 | .extract(), 55 | ['a', 'ab', 'abc']); 56 | }); 57 | 58 | }); 59 | 60 | }); 61 | -------------------------------------------------------------------------------- /src/duff.test.ts: -------------------------------------------------------------------------------- 1 | import { duff, duffbk, duffEach, duffReduce } from './duff'; 2 | 3 | describe('Unit: lib/duff', () => { 4 | describe('duff', () => { 5 | it('', () => { 6 | var count = 100; 7 | duff(count, i => { 8 | assert(i + count-- === 100); 9 | }); 10 | assert(count === 0); 11 | 12 | var count = 100; 13 | duff(-count, i => { 14 | assert(i + - --count === 0); 15 | }); 16 | assert(count === 0); 17 | }); 18 | 19 | }); 20 | 21 | describe('duffbk', () => { 22 | it('', () => { 23 | var count = 100; 24 | duffbk(count, i => { 25 | assert(i + count-- === 100); 26 | if (count === 50) { 27 | return false; 28 | } 29 | }); 30 | assert(count === 50); 31 | 32 | var count = 100; 33 | duffbk(-count, i => { 34 | assert(i + - --count === 0); 35 | if (count === 50) { 36 | return false; 37 | } 38 | }); 39 | assert(count === 50); 40 | 41 | }); 42 | 43 | }); 44 | 45 | describe('duffEach', () => { 46 | it('', () => { 47 | duffEach(Array.from(Array(100), (_, i) => i), (v, i) => { 48 | assert(v === i); 49 | }); 50 | }); 51 | 52 | }); 53 | 54 | describe('duffReduce', () => { 55 | it('', () => { 56 | assert(100 === duffReduce(Array.from(Array(100), (_, i) => i), (acc, v, i) => { 57 | assert(v === i); 58 | assert(acc++ === i); 59 | return acc; 60 | }, 0)); 61 | }); 62 | 63 | }); 64 | 65 | }); 66 | 67 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/drop.test.ts: -------------------------------------------------------------------------------- 1 | import { nat } from '../../../sequence.test'; 2 | 3 | describe('Unit: lib/monad/sequence/member/drop', () => { 4 | describe('drop', () => { 5 | it('-0 +0', () => { 6 | assert.deepStrictEqual( 7 | nat 8 | .drop(0) 9 | .take(0) 10 | .extract(), 11 | []); 12 | }); 13 | 14 | it('-0 +1', () => { 15 | assert.deepStrictEqual( 16 | nat 17 | .drop(0) 18 | .take(1) 19 | .extract(), 20 | [0]); 21 | }); 22 | 23 | it('-0 +2', () => { 24 | assert.deepStrictEqual( 25 | nat 26 | .drop(0) 27 | .take(2) 28 | .extract(), 29 | [0, 1]); 30 | }); 31 | 32 | it('-0 +3', () => { 33 | assert.deepStrictEqual( 34 | nat 35 | .drop(0) 36 | .take(3) 37 | .extract(), 38 | [0, 1, 2]); 39 | }); 40 | 41 | it('-1 +3', () => { 42 | assert.deepStrictEqual( 43 | nat 44 | .drop(1) 45 | .take(3) 46 | .extract(), 47 | [1, 2, 3]); 48 | }); 49 | 50 | it('-2 +3', () => { 51 | assert.deepStrictEqual( 52 | nat 53 | .drop(2) 54 | .take(3) 55 | .extract(), 56 | [2, 3, 4]); 57 | }); 58 | 59 | it('-3 +3', () => { 60 | assert.deepStrictEqual( 61 | nat 62 | .drop(3) 63 | .take(3) 64 | .extract(), 65 | [3, 4, 5]); 66 | }); 67 | 68 | }); 69 | 70 | }); 71 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/random.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { counter } from '../../../../counter'; 3 | 4 | describe('Unit: lib/monad/sequence/member/static/random', () => { 5 | describe('Sequence.random', () => { 6 | it('validate', () => { 7 | assert.deepStrictEqual( 8 | Sequence.random(counter()) 9 | .take(5) 10 | .subsequences() 11 | .filter(ns => ns.length === 2) 12 | .extract() 13 | .reduce((cnt, [n, m]) => n !== m ? ++cnt : cnt, 0), 14 | 10); 15 | 16 | assert.notDeepStrictEqual( 17 | Sequence.random() 18 | .take(9) 19 | .extract(), 20 | Sequence.random() 21 | .take(9) 22 | .extract()); 23 | }); 24 | 25 | it('number', () => { 26 | assert.deepStrictEqual( 27 | Sequence.random() 28 | .take(9) 29 | .map(n => n >= 0 && n < 1) 30 | .extract(), 31 | [true, true, true, true, true, true, true, true, true]); 32 | }); 33 | 34 | it('array', () => { 35 | assert.deepStrictEqual( 36 | Sequence.random([0, 1, 2]) 37 | .take(9) 38 | .map(n => { 39 | switch (n) { 40 | case 0: 41 | case 1: 42 | case 2: 43 | return true; 44 | default: 45 | return false; 46 | } 47 | }) 48 | .extract(), 49 | [true, true, true, true, true, true, true, true, true]); 50 | }); 51 | 52 | }); 53 | 54 | }); 55 | -------------------------------------------------------------------------------- /src/ascii.hpack.test.ts: -------------------------------------------------------------------------------- 1 | import { encode, decode } from './ascii.hpack'; 2 | import { xorshift } from './random'; 3 | 4 | describe('Unit: lib/ascii.hpack', () => { 5 | describe('encode/decode', () => { 6 | it('basic', () => { 7 | let input = ''; 8 | assert(input === decode(encode(input))); 9 | 10 | input = '0'; 11 | assert(input === decode(encode(input))); 12 | 13 | input = 'A'; 14 | assert(input === decode(encode(input))); 15 | 16 | input = 'a'; 17 | assert(input === decode(encode(input))); 18 | 19 | input = ' '; 20 | assert(input === decode(encode(input))); 21 | 22 | input = '|'; 23 | assert(input === decode(encode(input))); 24 | 25 | input = '00'; 26 | assert(input === decode(encode(input))); 27 | 28 | input = '02'; 29 | assert(input === decode(encode(input))); 30 | 31 | input = '0A'; 32 | assert(input === decode(encode(input))); 33 | }); 34 | 35 | for (let i = 0; i < 10; ++i) it(`verify ${i}`, function () { 36 | this.timeout(10 * 1e3); 37 | 38 | const input = [...Array(256)] 39 | .reduce((acc, _, i) => acc + String.fromCharCode(i), ''); 40 | assert(input === decode(encode(input))); 41 | const random = xorshift.random(3 ** i); 42 | for (let i = 0; i < 1e4; ++i) { 43 | const input = [...Array(random() * 32 + 1 | 0)] 44 | .reduce(acc => acc + String.fromCharCode(random() * 256 | 0), ''); 45 | const output = encode(input); 46 | assert(input === decode(output)); 47 | } 48 | }); 49 | 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /src/ascii.random.test.ts: -------------------------------------------------------------------------------- 1 | import { encode, decode } from './ascii.random'; 2 | import { xorshift } from './random'; 3 | 4 | describe('Unit: lib/ascii.random', () => { 5 | describe('encode/decode', () => { 6 | it('basic', () => { 7 | let input = ''; 8 | assert(input === decode(encode(input))); 9 | 10 | input = '0'; 11 | assert(input === decode(encode(input))); 12 | 13 | input = 'A'; 14 | assert(input === decode(encode(input))); 15 | 16 | input = 'a'; 17 | assert(input === decode(encode(input))); 18 | 19 | input = ' '; 20 | assert(input === decode(encode(input))); 21 | 22 | input = '|'; 23 | assert(input === decode(encode(input))); 24 | 25 | input = '00'; 26 | assert(input === decode(encode(input))); 27 | 28 | input = '02'; 29 | assert(input === decode(encode(input))); 30 | 31 | input = '0A'; 32 | assert(input === decode(encode(input))); 33 | }); 34 | 35 | for (let i = 0; i < 10; ++i) it(`verify ${i}`, function () { 36 | this.timeout(10 * 1e3); 37 | 38 | const input = [...Array(128)] 39 | .reduce((acc, _, i) => acc + String.fromCharCode(i), ''); 40 | assert(input === decode(encode(input))); 41 | const random = xorshift.random(3 ** i); 42 | for (let i = 0; i < 1e4; ++i) { 43 | const input = [...Array(random() * 32 + 1 | 0)] 44 | .reduce(acc => acc + String.fromCharCode(random() * 128 | 0), ''); 45 | const output = encode(input); 46 | assert(input === decode(output)); 47 | } 48 | }); 49 | 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /src/ascii.xpack.test.ts: -------------------------------------------------------------------------------- 1 | import { encode, decode } from './ascii.xpack'; 2 | import { xorshift } from './random'; 3 | 4 | describe('Unit: lib/ascii.xpack', () => { 5 | describe('encode/decode', () => { 6 | it('basic', () => { 7 | let input = ''; 8 | assert(input === decode(encode(input))); 9 | 10 | input = '0'; 11 | assert(input === decode(encode(input))); 12 | 13 | input = 'A'; 14 | assert(input === decode(encode(input))); 15 | 16 | input = 'a'; 17 | assert(input === decode(encode(input))); 18 | 19 | input = ' '; 20 | assert(input === decode(encode(input))); 21 | 22 | input = '|'; 23 | assert(input === decode(encode(input))); 24 | 25 | input = '00'; 26 | assert(input === decode(encode(input))); 27 | 28 | input = '02'; 29 | assert(input === decode(encode(input))); 30 | 31 | input = '0A'; 32 | assert(input === decode(encode(input))); 33 | }); 34 | 35 | for (let i = 0; i < 10; ++i) it(`verify ${i}`, function () { 36 | this.timeout(10 * 1e3); 37 | 38 | const input = [...Array(128)] 39 | .reduce((acc, _, i) => acc + String.fromCharCode(i), ''); 40 | assert(input === decode(encode(input))); 41 | const random = xorshift.random(3 ** i); 42 | for (let i = 0; i < 1e4; ++i) { 43 | const input = [...Array(random() * 32 + 1 | 0)] 44 | .reduce(acc => acc + String.fromCharCode(random() * 128 | 0), ''); 45 | const output = encode(input); 46 | assert(input === decode(output)); 47 | } 48 | }); 49 | 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /src/ascii.huffman.test.ts: -------------------------------------------------------------------------------- 1 | import { encode, decode } from './ascii.huffman'; 2 | import { xorshift } from './random'; 3 | 4 | describe('Unit: lib/ascii.huffman', () => { 5 | describe('encode/decode', () => { 6 | it('basic', () => { 7 | let input = ''; 8 | assert(input === decode(encode(input))); 9 | 10 | input = '0'; 11 | assert(input === decode(encode(input))); 12 | 13 | input = 'A'; 14 | assert(input === decode(encode(input))); 15 | 16 | input = 'a'; 17 | assert(input === decode(encode(input))); 18 | 19 | input = ' '; 20 | assert(input === decode(encode(input))); 21 | 22 | input = '|'; 23 | assert(input === decode(encode(input))); 24 | 25 | input = '00'; 26 | assert(input === decode(encode(input))); 27 | 28 | input = '02'; 29 | assert(input === decode(encode(input))); 30 | 31 | input = '0A'; 32 | assert(input === decode(encode(input))); 33 | }); 34 | 35 | for (let i = 0; i < 10; ++i) it(`verify ${i}`, function () { 36 | this.timeout(10 * 1e3); 37 | 38 | const input = [...Array(128)] 39 | .reduce((acc, _, i) => acc + String.fromCharCode(i), ''); 40 | assert(input === decode(encode(input))); 41 | const random = xorshift.random(3 ** i); 42 | for (let i = 0; i < 1e4; ++i) { 43 | const input = [...Array(random() * 32 + 1 | 0)] 44 | .reduce(acc => acc + String.fromCharCode(random() * 128 | 0), ''); 45 | const output = encode(input); 46 | assert(input === decode(output)); 47 | } 48 | }); 49 | 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/subsequences.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../core'; 2 | import { compose } from '../../../../helper/compose'; 3 | 4 | compose(Sequence, class extends Sequence { 5 | public override subsequences(): Sequence, Sequence.Iterator]> { 6 | return Sequence.mappend( 7 | Sequence.from([[]]), 8 | Sequence.from([0]) 9 | .bind(() => 10 | nonEmptySubsequences(this))); 11 | } 12 | }); 13 | 14 | function nonEmptySubsequences(xs: Sequence): Sequence, Sequence.Iterator]> { 15 | return Sequence.Iterator.when, Sequence.Iterator]>>( 16 | xs.iterate(), 17 | () => Sequence.mempty, 18 | xt => 19 | Sequence.mappend( 20 | Sequence.from([[Sequence.Thunk.value(xt)]]), 21 | new Sequence>, Sequence.Iterator]>, undefined>((_, cons) => 22 | Sequence.Iterator.when( 23 | xt, 24 | () => cons(), 25 | xt => 26 | cons( 27 | nonEmptySubsequences( 28 | Sequence.resume(Sequence.Thunk.iterator(xt))) 29 | .foldr((ys, r) => 30 | Sequence.mappend( 31 | Sequence.mappend( 32 | Sequence.from([ys]), 33 | Sequence.from([[Sequence.Thunk.value(xt), ...ys]])), 34 | r) 35 | , Sequence.mempty)))) 36 | .bind(xs => xs))); 37 | } 38 | -------------------------------------------------------------------------------- /src/coaggregator.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from './alias'; 2 | import { Coroutine, CoroutineOptions } from './coroutine'; 3 | import { AtomicPromise, never } from './promise'; 4 | import { select } from './select'; 5 | 6 | export class Coaggregator extends Coroutine { 7 | constructor( 8 | coroutines: Iterable>, 9 | reducer: (results: T[]) => T = results => results[0], 10 | opts?: CoroutineOptions, 11 | ) { 12 | super(async function* () { 13 | const cs = isArray(coroutines) 14 | ? coroutines 15 | : [...coroutines]; 16 | this.then( 17 | result => { 18 | for (const co of cs) { 19 | co[Coroutine.exit](result); 20 | } 21 | }, 22 | reason => { 23 | const rejection = AtomicPromise.reject(reason); 24 | for (const co of cs) { 25 | co[Coroutine.exit](rejection); 26 | } 27 | }); 28 | const results: T[] = []; 29 | for await (const { name, result } of select(cs)) { 30 | assert(Number.isSafeInteger(+name)); 31 | if (result.done) { 32 | results[name] = result.value; 33 | } 34 | else { 35 | yield result.value; 36 | } 37 | } 38 | assert(Object.keys(results).length === results.length); 39 | assert(results.length === cs.length); 40 | results.length === 0 41 | ? this[Coroutine.terminate](new Error(`Spica: Coaggregator: No result`)) 42 | : this[Coroutine.exit](reducer(results)); 43 | return never; 44 | }, { delay: false, ...opts }); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/dict/ixmap.ts: -------------------------------------------------------------------------------- 1 | import { IterableDict } from '../dict'; 2 | import { Index } from '../index'; 3 | 4 | export class IxMap implements IterableDict { 5 | constructor( 6 | entries?: Iterable, 7 | ) { 8 | if (entries) for (const { 0: k, 1: v } of entries) { 9 | this.set(k, v); 10 | } 11 | } 12 | private readonly index = new Index(); 13 | private indexes = new Map(); 14 | private values: V[] = Array(16); 15 | public get size(): number { 16 | return this.indexes.size; 17 | } 18 | public has(key: K): boolean { 19 | return this.indexes.has(key); 20 | } 21 | public get(key: K): V | undefined { 22 | return this.values[this.indexes.get(key) ?? this.values.length]; 23 | } 24 | public set(key: K, value: V): this { 25 | let index = this.indexes.get(key) ?? -1; 26 | if (index === -1) { 27 | index = this.index.pop(); 28 | this.indexes.set(key, index); 29 | } 30 | this.values[index] = value; 31 | return this; 32 | } 33 | public delete(key: K, index?: number): boolean { 34 | index ??= this.indexes.get(key) ?? -1; 35 | if (index === -1) return false; 36 | this.index.push(index); 37 | this.values[index] = undefined as V; 38 | return this.indexes.delete(key); 39 | } 40 | public clear(): void { 41 | this.index.clear(); 42 | this.indexes = new Map(); 43 | this.values = Array(16); 44 | } 45 | public *[Symbol.iterator](): Iterator<[K, V], undefined, undefined> { 46 | const { indexes, values } = this; 47 | for (const { 0: key, 1: index } of indexes) { 48 | yield [key, values[index]]; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/helper/compose.test.ts: -------------------------------------------------------------------------------- 1 | import { compose } from './compose'; 2 | 3 | describe('Unit: lib/helper/compose', () => { 4 | describe('compose', () => { 5 | it('1', () => { 6 | class A { 7 | static a = 'A'; 8 | ap = 'a'; 9 | am() { 10 | return this.ap; 11 | } 12 | } 13 | interface X extends A { 14 | } 15 | class X { 16 | static x = 'X'; 17 | xp = 'x'; 18 | xm() { 19 | return this.xp; 20 | } 21 | } 22 | compose(X, A as any); 23 | const x = new X(); 24 | assert(x instanceof X); 25 | assert(X['a'] === 'A'); 26 | assert(X.x === 'X'); 27 | assert(x.xp === 'x'); 28 | assert(x.am() === undefined); 29 | assert(x.xm() === 'x'); 30 | }); 31 | 32 | it('2', () => { 33 | class A { 34 | static a = 'A'; 35 | ap = 'a'; 36 | am() { 37 | return this.ap; 38 | } 39 | } 40 | class B { 41 | static b = 'B'; 42 | bp = 'b'; 43 | bm() { 44 | return this.bp; 45 | } 46 | } 47 | interface X extends B, A { 48 | } 49 | class X { 50 | static x = 'X'; 51 | xp = 'x'; 52 | xm() { 53 | return this.xp; 54 | } 55 | } 56 | compose(X, A as any, B as any); 57 | const x = new X(); 58 | assert(x instanceof X); 59 | assert(X['a'] === 'A'); 60 | assert(X['b'] === 'B'); 61 | assert(X.x === 'X'); 62 | assert(x.xp === 'x'); 63 | assert(x.am() === undefined); 64 | assert(x.bm() === undefined); 65 | assert(x.xm() === 'x'); 66 | }); 67 | 68 | }); 69 | 70 | }); 71 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/zip.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { nat } from '../../../sequence.test'; 3 | 4 | describe('Unit: lib/monad/sequence/member/static/zip', () => { 5 | describe('Sequence.zip', () => { 6 | it('unlimited', () => { 7 | const even = nat.filter(n => n % 2 === 0); 8 | const odd = nat.filter(n => n % 2 === 1); 9 | assert.deepStrictEqual( 10 | Sequence.zip(odd, even).take(3).extract(), 11 | [[1, 0], [3, 2], [5, 4]]); 12 | }); 13 | 14 | it('same', () => { 15 | const nat = new Sequence((n = 0, cons) => n < 3 ? cons(n, n + 1) : cons(n)); 16 | const even = nat.filter(n => n % 2 === 0); 17 | const odd = nat.filter(n => n % 2 === 1); 18 | assert.deepStrictEqual( 19 | Sequence.zip(odd, even).take(3).extract(), 20 | [[1, 0], [3, 2]]); 21 | }); 22 | 23 | it('mismatch', () => { 24 | const neg = new Sequence((n = 0, cons) => n < 1 ? cons(-n, n + 1) : cons(-n)); 25 | assert.deepStrictEqual( 26 | Sequence.zip(nat, neg).take(3).extract(), 27 | [[0, 0], [1, -1]]); 28 | assert.deepStrictEqual( 29 | Sequence.zip(neg, nat).take(3).extract(), 30 | [[0, 0], [-1, 1]]); 31 | }); 32 | 33 | it('empty', () => { 34 | assert.deepStrictEqual( 35 | Sequence.zip(nat, Sequence.from([])).take(3).extract(), 36 | []); 37 | assert.deepStrictEqual( 38 | Sequence.zip(Sequence.from([]), nat).take(3).extract(), 39 | []); 40 | assert.deepStrictEqual( 41 | Sequence.zip(Sequence.from([]), Sequence.from([])).take(3).extract(), 42 | []); 43 | }); 44 | 45 | }); 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /benchmark/url.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { ReadonlyURL, URL } from '../src/url'; 3 | 4 | describe('Benchmark:', function () { 5 | const { origin } = location; 6 | 7 | describe('URL', function () { 8 | describe('native', function () { 9 | const url = new globalThis.URL('', origin); 10 | 11 | it('new', function (done) { 12 | benchmark('URL native new', () => new globalThis.URL('', origin), done); 13 | }); 14 | 15 | it('href', function (done) { 16 | benchmark('URL native href', () => url.href, done); 17 | }); 18 | 19 | it('origin', function (done) { 20 | benchmark('URL native origin', () => url.origin, done); 21 | }); 22 | 23 | }); 24 | 25 | describe('readonly', function () { 26 | const url = new ReadonlyURL('', origin); 27 | 28 | it('new', function (done) { 29 | benchmark('URL readonly new', () => new ReadonlyURL('', origin), done); 30 | }); 31 | 32 | it('href', function (done) { 33 | benchmark('URL readonly href', () => url.href, done); 34 | }); 35 | 36 | it('origin', function (done) { 37 | benchmark('URL readonly origin', () => url.origin, done); 38 | }); 39 | 40 | }); 41 | 42 | describe('custom', function () { 43 | const url = new URL('', origin); 44 | 45 | it('new', function (done) { 46 | benchmark('URL custom new', () => new URL('', origin), done); 47 | }); 48 | 49 | it('href', function (done) { 50 | benchmark('URL custom href', () => url.href, done); 51 | }); 52 | 53 | it('origin', function (done) { 54 | benchmark('URL custom origin', () => url.origin, done); 55 | }); 56 | 57 | }); 58 | 59 | }); 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /src/list/hlist.ts: -------------------------------------------------------------------------------- 1 | import { Tail, Reverse } from '../type'; 2 | import { unshift } from '../array'; 3 | 4 | export type HList = 5 | as extends readonly [] ? HNil : 6 | HCons; 7 | export function HList(...as: as): HList; 8 | export function HList(...as: as): HList { 9 | return as.reduceRight>((node, a) => node.add(a), HNil as any); 10 | } 11 | 12 | type HNil = typeof HNil; 13 | const HNil = new class HNil { 14 | public add(a: a): HCons<[a]> { 15 | return new HCons(a, this); 16 | } 17 | public reverse(): [] { 18 | return []; 19 | } 20 | public tuple(): [] { 21 | return []; 22 | } 23 | }(); 24 | 25 | class HCons { 26 | constructor( 27 | public readonly head: as[0], 28 | public readonly tail: as extends readonly [unknown, unknown, ...unknown[]] ? HCons> : HNil, 29 | ) { 30 | } 31 | public add(a: a): HCons<[a, ...as]> { 32 | // @ts-ignore 33 | return new HCons(a, this); 34 | } 35 | public modify(f: (a: as[0]) => a): HCons<[a, ...Tail]> { 36 | // @ts-ignore 37 | return this.tail.add(f(this.head)); 38 | } 39 | public fold(this: HCons<[unknown, unknown, ...unknown[]]>, f: (l: as[0], r: as[1]) => a): HCons<[a, ...Tail>]> { 40 | // @ts-ignore 41 | return this.tail.modify(r => f(this.head, r)); 42 | } 43 | public unfold(f: (a: as[0]) => a): HCons<[a, ...as]> { 44 | // @ts-ignore 45 | return this.add(f(this.head)); 46 | } 47 | public reverse(): Reverse { 48 | return this.tuple().reverse() as Reverse; 49 | } 50 | public tuple(): as { 51 | return unshift([this.head], this.tail.tuple()) as as; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/dict/attrmap.test.ts: -------------------------------------------------------------------------------- 1 | import { AttrMap } from './attrmap'; 2 | import { DataMap } from './datamap'; 3 | 4 | describe('Unit: lib/attrmap', () => { 5 | describe('AttrMap', () => { 6 | it('initialize', () => { 7 | const map = new AttrMap(<[number, number, number][]>[[1, 2, 3]], Map); 8 | assert(map.get(1, 2) === 3); 9 | }); 10 | 11 | it('get/set', () => { 12 | const map = new AttrMap([], Map); 13 | assert(map.get(0, 0) === undefined); 14 | assert(map.set(0, 0, '') === map); 15 | assert(map.get(0, 0) === ''); 16 | assert(map.get(1, 0) === undefined); 17 | assert(map.set(1, 0, ' ') === map); 18 | assert(map.get(1, 0) === ' '); 19 | assert(map.get(0, 0) === ''); 20 | assert(map.set(0, 0, ' ') === map); 21 | assert(map.get(0, 0) === ' '); 22 | }); 23 | 24 | it('has', () => { 25 | const map = new AttrMap<{}, number, string>([], Map); 26 | assert(map.has(0, 0) === false); 27 | assert(map.set(0, 0, '') === map); 28 | assert(map.has(0, 0) === true); 29 | assert(map.has(1, 0) === false); 30 | }); 31 | 32 | it('delete', () => { 33 | const map = new AttrMap<{}, number, string>([], Map); 34 | assert(map.set(0, 0, '') === map); 35 | assert(map.delete(0, 0) === true); 36 | assert(map.has(0, 0) === false); 37 | assert(map.delete(0, 0) === false); 38 | assert(map.delete(0) === true); 39 | assert(map.has(0, 0) === false); 40 | assert(map.delete(0) === false); 41 | }); 42 | 43 | it('injection', () => { 44 | const map = new AttrMap<{}, number[], number>([], DataMap, DataMap); 45 | assert(map.set({}, [0], 0) === map); 46 | assert(map.get({}, [0]) === 0); 47 | }); 48 | 49 | }); 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /src/channel.test.ts: -------------------------------------------------------------------------------- 1 | import { Channel } from './channel'; 2 | import { wait } from './timer'; 3 | 4 | describe('Unit: lib/channel', function () { 5 | describe('channel', function () { 6 | it('buffer 0', async function () { 7 | assert(await Promise.race([new Channel().take(), wait(1).then(() => -1)]) === -1); 8 | assert(await Promise.race([new Channel().put(), wait(1).then(() => -1)]) === -1); 9 | await (async ch => { 10 | assert(await Promise.race([ch.put(0), wait(1).then(() => -1)]) === -1); 11 | assert(await ch.take() === 0); 12 | assert(await Promise.race([ch.take(), wait(1).then(() => -1)]) === -1); 13 | })(new Channel()); 14 | await (async ch => { 15 | assert(await Promise.race([ch.take(), wait(1).then(() => -1)]) === -1); 16 | assert(await ch.put(0) === undefined); 17 | assert(await Promise.race([ch.put(0), wait(1).then(() => -1)]) === -1); 18 | })(new Channel()); 19 | }); 20 | 21 | it('buffer 1', async function () { 22 | assert(await Promise.race([new Channel(1).take(), wait(1).then(() => -1)]) === -1); 23 | assert(await Promise.race([new Channel(1).put(), wait(1).then(() => -1)]) === undefined); 24 | await (async ch => { 25 | assert(await Promise.race([ch.put(0), wait(1).then(() => -1)]) === undefined); 26 | assert(await ch.take() === 0); 27 | assert(await Promise.race([ch.take(), wait(1).then(() => -1)]) === -1); 28 | })(new Channel(1)); 29 | await (async ch => { 30 | assert(await Promise.race([ch.take(), wait(1).then(() => -1)]) === -1); 31 | assert(await ch.put(0) === undefined); 32 | assert(await Promise.race([ch.put(0), wait(1).then(() => -1)]) === undefined); 33 | })(new Channel(1)); 34 | }); 35 | 36 | }); 37 | 38 | }); 39 | -------------------------------------------------------------------------------- /src/copropagator.test.ts: -------------------------------------------------------------------------------- 1 | import { Copropagator } from './copropagator'; 2 | import { Coroutine } from './coroutine'; 3 | import { never } from './promise'; 4 | 5 | describe('Unit: lib/copropagator', () => { 6 | describe('Copropagator', () => { 7 | it('exit', () => { 8 | let cnt = 0; 9 | const co = new Copropagator([ 10 | new Coroutine(async function* () { 11 | this.then(reason => { 12 | assert(reason === 0); 13 | assert(cnt === 0 && ++cnt); 14 | }); 15 | return never; 16 | }, { delay: false }), 17 | new Coroutine(async function* () { 18 | this.then(reason => { 19 | assert(reason === 0); 20 | assert(cnt === 1 && ++cnt); 21 | }); 22 | return never; 23 | }, { delay: false }), 24 | ]); 25 | co.then(reason => { 26 | assert(reason === 0); 27 | assert(cnt === 2 && ++cnt); 28 | }); 29 | co[Coroutine.exit](0); 30 | assert(cnt === 3 && ++cnt); 31 | }); 32 | 33 | it('terminate', () => { 34 | let cnt = 0; 35 | const co = new Copropagator([ 36 | new Coroutine(async function* () { 37 | this.catch(reason => { 38 | assert(reason === 0); 39 | assert(cnt === 0 && ++cnt); 40 | }); 41 | return never; 42 | }, { delay: false }), 43 | new Coroutine(async function* () { 44 | this.catch(reason => { 45 | assert(reason === 0); 46 | assert(cnt === 1 && ++cnt); 47 | }); 48 | return never; 49 | }, { delay: false }), 50 | ]); 51 | co.catch(reason => { 52 | assert(reason === 0); 53 | assert(cnt === 2 && ++cnt); 54 | }); 55 | co[Coroutine.terminate](0); 56 | assert(cnt === 3 && ++cnt); 57 | }); 58 | 59 | }); 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /src/dict/multimap.test.ts: -------------------------------------------------------------------------------- 1 | import { MultiMap } from './multimap'; 2 | 3 | describe('Unit: lib/multimap', () => { 4 | describe('MultiMap', () => { 5 | it('has', () => { 6 | const map = new MultiMap([[0, 1], [0, 2], [1, 0]]); 7 | assert(map.has(0) === true); 8 | assert(map.has(1) === true); 9 | assert(map.has(2) === false); 10 | }); 11 | 12 | it('delete', () => { 13 | const map = new MultiMap([[0, 1]]); 14 | assert(map.has(0) === true); 15 | assert(map.delete(0) === true); 16 | assert(map.has(0) === false); 17 | assert(map.delete(0) === false); 18 | assert(map.delete(1) === false); 19 | }); 20 | 21 | it('get/set/ref', () => { 22 | const map = new MultiMap(); 23 | assert.deepStrictEqual([...map.ref(0)], []); 24 | assert(map.get(0) === undefined); 25 | assert(map.set(0, 1) === map); 26 | assert(map.get(0) === 1); 27 | assert.deepStrictEqual([...map.ref(0)], [1]); 28 | assert(map.set(0, 2) === map); 29 | assert(map.get(0) === 1); 30 | assert.deepStrictEqual([...map.ref(0)], [1, 2]); 31 | }); 32 | 33 | it('take', () => { 34 | const map = new MultiMap(); 35 | assert.deepStrictEqual([...map.take(0, 0)], []); 36 | assert(map.has(0) === false); 37 | map.set(0, 1); 38 | assert.deepStrictEqual([...map.take(0, 0)], []); 39 | assert.deepStrictEqual([...map.take(0, 1)], [1]); 40 | assert.deepStrictEqual([...map.ref(0)], []); 41 | map.set(0, 1); 42 | map.set(0, 2); 43 | assert.deepStrictEqual([...map.take(0, 1)], [1]); 44 | assert.deepStrictEqual([...map.ref(0)], [2]); 45 | map.set(0, 1); 46 | assert.deepStrictEqual([...map.take(0, Infinity)], [2, 1]); 47 | assert.deepStrictEqual([...map.ref(0)], []); 48 | }); 49 | 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /benchmark/duff.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { duff, duffbk, duffEach, duffReduce } from '../src/duff'; 3 | 4 | describe('Benchmark:', function () { 5 | describe('Duff', function () { 6 | for (const size of [1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6]) { 7 | it(`for ${size.toLocaleString('en')}`, function (done) { 8 | benchmark(`for ${size.toLocaleString('en')}`, () => { 9 | for (let i = 0; i < size; ++i); 10 | }, done); 11 | }); 12 | 13 | it(`duff ${size.toLocaleString('en')}`, function (done) { 14 | benchmark(`duff ${size.toLocaleString('en')}`, () => duff(size, i => i), done); 15 | }); 16 | 17 | it(`duffbk ${size.toLocaleString('en')}`, function (done) { 18 | benchmark(`duffbk ${size.toLocaleString('en')}`, () => duffbk(size, i => i), done); 19 | }); 20 | 21 | it(`for array ${size.toLocaleString('en')}`, function (done) { 22 | const as = Array(size).fill(0); 23 | benchmark(`for array ${size.toLocaleString('en')}`, () => { 24 | for (let i = 0; i < size; ++i) as[i]; 25 | }, done); 26 | }); 27 | 28 | it(`duff array ${size.toLocaleString('en')}`, function (done) { 29 | const as = Array(size).fill(0); 30 | benchmark(`duff array ${size.toLocaleString('en')}`, () => duff(as.length, i => as[i]), done); 31 | //benchmark(`duff array ${size.toLocaleString('en')}`, () => duffbk(as.length, i => as[i]), done); 32 | }); 33 | 34 | it(`duffEach ${size.toLocaleString('en')}`, function (done) { 35 | const as = Array(size).fill(0); 36 | benchmark(`duffEach ${size.toLocaleString('en')}`, () => duffEach(as, v => v), done); 37 | }); 38 | 39 | it(`duffReduce ${size.toLocaleString('en')}`, function (done) { 40 | const as = Array(size).fill(0); 41 | benchmark(`duffReduce ${size.toLocaleString('en')}`, () => duffReduce(as, v => v, 0), done); 42 | }); 43 | } 44 | 45 | }); 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/intersect.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { nat } from '../../../sequence.test'; 3 | 4 | describe('Unit: lib/monad/sequence/member/intersect/', () => { 5 | const double = nat.map(n => n * 2); 6 | const triple = nat.map(n => n * 3); 7 | 8 | describe('Sequence.intersect', () => { 9 | it('unlimited', () => { 10 | assert.deepStrictEqual( 11 | Sequence.intersect(double, triple, (a, b) => a - b).take(3).extract(), 12 | [0, 6, 12]); 13 | assert.deepStrictEqual( 14 | Sequence.intersect(double.map(n => -n), triple.map(n => -n), (a, b) => b - a).take(3).extract(), 15 | [0, 6, 12].map(n => -n)); 16 | }); 17 | 18 | it('same', () => { 19 | assert.deepStrictEqual( 20 | Sequence.intersect(double, triple, (a, b) => a - b).take(3).extract(), 21 | [0, 6, 12]); 22 | assert.deepStrictEqual( 23 | Sequence.intersect(double.map(n => -n), triple.map(n => -n), (a, b) => b - a).take(3).extract(), 24 | [0, 6, 12].map(n => -n)); 25 | }); 26 | 27 | it('mismatch', () => { 28 | assert.deepStrictEqual( 29 | Sequence.intersect(double.dropWhile(n => n < 6).takeUntil(n => n === 12), triple, (a, b) => a - b).take(2).extract(), 30 | [6, 12]); 31 | assert.deepStrictEqual( 32 | Sequence.intersect(triple, double.dropWhile(n => n < 6).takeUntil(n => n === 12), (a, b) => a - b).take(2).extract(), 33 | [6, 12]); 34 | }); 35 | 36 | it('empty', () => { 37 | assert.deepStrictEqual( 38 | Sequence.intersect(nat, Sequence.from([]), (a, b) => a - b).take(3).extract(), 39 | []); 40 | assert.deepStrictEqual( 41 | Sequence.intersect(Sequence.from([]), nat, (a, b) => a - b).take(3).extract(), 42 | []); 43 | assert.deepStrictEqual( 44 | Sequence.intersect(Sequence.from([]), Sequence.from([]), () => 0).take(3).extract(), 45 | []); 46 | }); 47 | 48 | }); 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /benchmark/chrono.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { clock } from '../src/chrono'; 3 | 4 | describe.skip('Benchmark:', function () { 5 | describe('Chrono', function () { 6 | for (const size of [1, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6]) { 7 | it(`resolve ${size.toLocaleString('en')}`, function (done) { 8 | benchmark(`Chrono resolve ${size.toLocaleString('en')}`, done => { 9 | for (let i = 0; i < size; ++i) { 10 | Promise.resolve().then(() => 0); 11 | } 12 | Promise.resolve().then(done); 13 | }, done, { defer: true }); 14 | }); 15 | 16 | it(`then ${size.toLocaleString('en')}`, function (done) { 17 | const p = Promise.resolve(); 18 | benchmark(`Chrono then ${size.toLocaleString('en')}`, done => { 19 | for (let i = 0; i < size; ++i) { 20 | p.then(() => 0); 21 | } 22 | p.then(done); 23 | }, done, { defer: true }); 24 | }); 25 | 26 | it(`next ${size.toLocaleString('en')}`, function (done) { 27 | benchmark(`Chrono next ${size.toLocaleString('en')}`, done => { 28 | for (let i = 0; i < size; ++i) { 29 | clock.next(() => 0); 30 | } 31 | clock.next(done); 32 | }, done, { defer: true }); 33 | }); 34 | 35 | it(`micro ${size.toLocaleString('en')}`, function (done) { 36 | benchmark(`Chrono micro ${size.toLocaleString('en')}`, done => { 37 | for (let i = 0; i < size; ++i) { 38 | queueMicrotask(() => 0); 39 | } 40 | queueMicrotask(done); 41 | }, done, { defer: true }); 42 | }); 43 | 44 | it(`now ${size.toLocaleString('en')}`, function (done) { 45 | benchmark(`Chrono now ${size.toLocaleString('en')}`, done => { 46 | for (let i = 0; i < size; ++i) { 47 | clock.now(() => 0); 48 | } 49 | clock.now(done); 50 | }, done, { defer: true }); 51 | }); 52 | } 53 | }); 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/union.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { nat } from '../../../sequence.test'; 3 | 4 | describe('Unit: lib/monad/sequence/member/static/union', () => { 5 | const double = nat.map(n => n * 2); 6 | const triple = nat.map(n => n * 3); 7 | 8 | describe('Sequence.union', () => { 9 | it('unlimited', () => { 10 | assert.deepStrictEqual( 11 | Sequence.union(double, triple, (a, b) => a - b).take(7).extract(), 12 | [0, 2, 3, 4, 6, 8, 9]); 13 | assert.deepStrictEqual( 14 | Sequence.union(double.map(n => -n), triple.map(n => -n), (a, b) => b - a).take(7).extract(), 15 | [0, 2, 3, 4, 6, 8, 9].map(n => -n)); 16 | }); 17 | 18 | it('same', () => { 19 | assert.deepStrictEqual( 20 | Sequence.union(double, triple, (a, b) => a - b).take(7).extract(), 21 | [0, 2, 3, 4, 6, 8, 9]); 22 | assert.deepStrictEqual( 23 | Sequence.union(double.map(n => -n), triple.map(n => -n), (a, b) => b - a).take(7).extract(), 24 | [0, 2, 3, 4, 6, 8, 9].map(n => -n)); 25 | }); 26 | 27 | it('mismatch', () => { 28 | assert.deepStrictEqual( 29 | Sequence.union(double.dropWhile(n => n < 6).takeUntil(n => n === 12), triple, (a, b) => a - b).take(8).extract(), 30 | [0, 3, 6, 8, 9, 10, 12, 15]); 31 | assert.deepStrictEqual( 32 | Sequence.union(triple, double.dropWhile(n => n < 6).takeUntil(n => n === 12), (a, b) => a - b).take(8).extract(), 33 | [0, 3, 6, 8, 9, 10, 12, 15]); 34 | }); 35 | 36 | it('empty', () => { 37 | assert.deepStrictEqual( 38 | Sequence.union(nat, Sequence.from([]), (a, b) => a - b).take(3).extract(), 39 | [0, 1, 2]); 40 | assert.deepStrictEqual( 41 | Sequence.union(Sequence.from([]), nat, (a, b) => a - b).take(3).extract(), 42 | [0, 1, 2]); 43 | assert.deepStrictEqual( 44 | Sequence.union(Sequence.from([]), Sequence.from([]), () => 0).take(3).extract(), 45 | []); 46 | }); 47 | 48 | }); 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /src/select.ts: -------------------------------------------------------------------------------- 1 | interface AsyncIterable { 2 | [Symbol.asyncIterator](): AsyncIterator; 3 | } 4 | 5 | type Channel = 6 | | AsyncIterable 7 | | (() => AsyncIterable); 8 | type Channels = 9 | | readonly [] | readonly [Channel, ...Channel[]] 10 | | readonly Channel[] 11 | | Record; 12 | type ChannelIteratorResult = 13 | C extends () => AsyncIterable ? IteratorResult : 14 | C extends AsyncIterable ? IteratorResult : 15 | never; 16 | type Selection = 17 | T extends readonly unknown[] ? number extends T['length'] ? 18 | { readonly name: string; readonly result: T[number] extends Channel ? ChannelIteratorResult : never; } : 19 | { [P in keyof T]: T[P] extends Channel ? { readonly name: P; readonly result: ChannelIteratorResult; } : never; }[number] : 20 | { [P in keyof T]: T[P] extends Channel ? { readonly name: P; readonly result: ChannelIteratorResult; } : never; }[keyof T]; 21 | 22 | export async function* select( 23 | channels: T, 24 | ): AsyncGenerator, undefined, undefined> { 25 | const reqs = new Set(Object.entries(channels) 26 | .map(([name, chan]) => ( 27 | chan = typeof chan === 'function' ? chan() : chan, 28 | take(name, chan[Symbol.asyncIterator]())))); 29 | while (reqs.size > 0) { 30 | const { 0: req, 1: name, 2: chan, 3: result } = await Promise.race(reqs); 31 | assert(reqs.has(req)); 32 | reqs.delete(req); 33 | !result.done && reqs.add(take(name, chan)); 34 | yield { name, result } as Selection; 35 | } 36 | } 37 | 38 | type Request = Promise, IteratorResult]>; 39 | function take(name: string, chan: AsyncIterator): Request { 40 | const req: Request = chan.next().then(result => [req, name, chan, result]); 41 | return req; 42 | } 43 | -------------------------------------------------------------------------------- /benchmark/channel.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { Channel } from '../src/channel'; 3 | 4 | describe('Benchmark:', function () { 5 | describe('Channel', function () { 6 | it('new', function (done) { 7 | benchmark('Channel new', () => new Channel(), done); 8 | }); 9 | 10 | it('0 put/take', function (done) { 11 | const ch = new Channel(); 12 | benchmark('Channel 0 put/take', () => (ch.put(), ch.take()), done); 13 | }); 14 | 15 | it('1 put/take', function (done) { 16 | const ch = new Channel(1); 17 | benchmark('Channel 1 put/take', () => (ch.put(), ch.take()), done); 18 | }); 19 | 20 | it('0 take/put', function (done) { 21 | const ch = new Channel(); 22 | benchmark('Channel 0 take/put', () => (ch.take(), ch.put()), done); 23 | }); 24 | 25 | it('1 take/put', function (done) { 26 | const ch = new Channel(1); 27 | benchmark('Channel 1 take/put', () => (ch.take(), ch.put()), done); 28 | }); 29 | 30 | it('0 put/iterate', function (done) { 31 | const ch = new Channel(); 32 | const iter = ch[Symbol.asyncIterator](); 33 | benchmark('Channel 0 put/iterate', done => (ch.put(), iter.next().then(done)), done, { defer: true }); 34 | }); 35 | 36 | it('1 put/iterate', function (done) { 37 | const ch = new Channel(1); 38 | const iter = ch[Symbol.asyncIterator](); 39 | benchmark('Channel 1 put/iterate', done => (ch.put(), iter.next().then(done)), done, { defer: true }); 40 | }); 41 | 42 | it('0 iterate/put', function (done) { 43 | const ch = new Channel(); 44 | const iter = ch[Symbol.asyncIterator](); 45 | benchmark('Channel 0 iterate/put', done => (iter.next(), ch.put().then(done)), done, { defer: true }); 46 | }); 47 | 48 | it('1 iterate/put', function (done) { 49 | const ch = new Channel(1); 50 | const iter = ch[Symbol.asyncIterator](); 51 | benchmark('Channel 1 iterate/put', done => (iter.next(), ch.put().then(done)), done, { defer: true }); 52 | }); 53 | 54 | }); 55 | 56 | }); 57 | -------------------------------------------------------------------------------- /benchmark/cancellation.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { Cancellation } from '../src/cancellation'; 3 | 4 | describe('Benchmark:', function () { 5 | describe('Cancellation', function () { 6 | it('new', function (done) { 7 | benchmark('Cancellation new', () => new Cancellation(), done); 8 | }); 9 | 10 | it('register 1', function (done) { 11 | benchmark('Cancellation register 1', () => { 12 | const c = new Cancellation(); 13 | c.register(() => 0); 14 | }, done); 15 | }); 16 | 17 | it('register 3', function (done) { 18 | benchmark('Cancellation register 3', () => { 19 | const c = new Cancellation(); 20 | c.register(() => 0); 21 | c.register(() => 0); 22 | c.register(() => 0); 23 | }, done); 24 | }); 25 | 26 | it('register 5', function (done) { 27 | benchmark('Cancellation register 5', () => { 28 | const c = new Cancellation(); 29 | c.register(() => 0); 30 | c.register(() => 0); 31 | c.register(() => 0); 32 | c.register(() => 0); 33 | c.register(() => 0); 34 | }, done); 35 | }); 36 | 37 | it('cancel 1', function (done) { 38 | benchmark('Cancellation cancel 1', () => { 39 | const c = new Cancellation(); 40 | c.register(() => 0); 41 | c.cancel(); 42 | }, done); 43 | }); 44 | 45 | it('cancel 3', function (done) { 46 | benchmark('Cancellation cancel 3', () => { 47 | const c = new Cancellation(); 48 | c.register(() => 0); 49 | c.register(() => 0); 50 | c.register(() => 0); 51 | c.cancel(); 52 | }, done); 53 | }); 54 | 55 | it('cancel 5', function (done) { 56 | benchmark('Cancellation cancel 5', () => { 57 | const c = new Cancellation(); 58 | c.register(() => 0); 59 | c.register(() => 0); 60 | c.register(() => 0); 61 | c.register(() => 0); 62 | c.register(() => 0); 63 | c.cancel(); 64 | }, done); 65 | }); 66 | 67 | }); 68 | 69 | }); 70 | -------------------------------------------------------------------------------- /src/alias.ts: -------------------------------------------------------------------------------- 1 | export const { 2 | MAX_SAFE_INTEGER, 3 | MAX_VALUE, 4 | MIN_SAFE_INTEGER, 5 | MIN_VALUE, 6 | EPSILON, 7 | isFinite, 8 | isInteger, 9 | isNaN, 10 | isSafeInteger, 11 | parseFloat, 12 | parseInt, 13 | } = Number; 14 | 15 | export const { 16 | PI, 17 | abs, 18 | ceil, 19 | floor, 20 | max, 21 | min, 22 | random, 23 | round, 24 | sign, 25 | cos, 26 | tan, 27 | log, 28 | log2, 29 | log10, 30 | sqrt, 31 | } = Math; 32 | 33 | export const isArray: { 34 | (arg: ArrayLike | Iterable): arg is T[]; 35 | (arg: any): arg is any[]; 36 | } = Array.isArray; 37 | 38 | export const hasOwnProperty = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty) as (target: unknown, prop: string | number | symbol) => boolean; 39 | export const isPrototypeOf = Object.prototype.isPrototypeOf.call.bind(Object.prototype.isPrototypeOf) as (target: unknown, base: unknown) => boolean; 40 | export const isEnumerable = Object.prototype.propertyIsEnumerable.call.bind(Object.prototype.propertyIsEnumerable) as (target: unknown, prop: string | number | symbol) => boolean; 41 | export const toString = Object.prototype.toString.call.bind(Object.prototype.toString) as (target: unknown) => string; 42 | export const ObjectAssign: { 43 | (target: T, source: Partial, ...sources: Partial[]): T; 44 | (target: Partial, source1: T, source2: Partial, ...sources: Partial[]): T; 45 | (target: T, ...sources: Partial[]): T; 46 | (target: Partial, source1: T, source2: Partial, ...sources: Partial[]): T; 47 | } = Object.assign; 48 | export const ObjectCreate: { 49 | (o: null, properties?: PropertyDescriptorMap & ThisType): object; 50 | (o: T, properties?: PropertyDescriptorMap & ThisType): T; 51 | } = Object.create; 52 | export const ObjectGetPrototypeOf: (o: unknown) => object | null = Object.getPrototypeOf; 53 | export const ObjectSetPrototypeOf: (o: T, proto: object | null) => T = Object.setPrototypeOf; 54 | -------------------------------------------------------------------------------- /src/sort.test.ts: -------------------------------------------------------------------------------- 1 | import { sort } from './sort'; 2 | import { Sequence } from './monad/sequence'; 3 | 4 | describe('Unit: lib/sort', () => { 5 | describe('sort', () => { 6 | 7 | function cmp(a: number, b: number): number { 8 | return a - b; 9 | } 10 | 11 | it('0', () => { 12 | assert.deepStrictEqual( 13 | sort([3, 1, 2], cmp, 0, true), 14 | [3, 1, 2]); 15 | assert.deepStrictEqual( 16 | sort([3, 1, 2], cmp, 0), 17 | [3, 1, 2]); 18 | }); 19 | 20 | it('1', () => { 21 | assert.deepStrictEqual( 22 | sort([3, 1, 2], cmp, 1, true), 23 | [1, 3, 2]); 24 | assert.deepStrictEqual( 25 | sort([3, 1, 2], cmp, 1), 26 | [1, 3, 2]); 27 | }); 28 | 29 | it('2', () => { 30 | assert.deepStrictEqual( 31 | sort([3, 1, 2], cmp, 2, true), 32 | [1, 2, 3]); 33 | assert.deepStrictEqual( 34 | sort([3, 1, 2], cmp, 2), 35 | [1, 2, 3]); 36 | }); 37 | 38 | it('3', () => { 39 | assert.deepStrictEqual( 40 | sort([3, 1, 2], cmp, 3, true), 41 | [1, 2, 3]); 42 | assert.deepStrictEqual( 43 | sort([3, 1, 2], cmp, 3), 44 | [1, 2, 3]); 45 | }); 46 | 47 | it('4', () => { 48 | assert.deepStrictEqual( 49 | sort([3, 1, 2], cmp, 4, true), 50 | [1, 2, 3]); 51 | assert.deepStrictEqual( 52 | sort([3, 1, 2], cmp, 4), 53 | [1, 2, 3]); 54 | }); 55 | 56 | it('Infinity', () => { 57 | assert.deepStrictEqual( 58 | sort([3, 1, 2], cmp, Infinity, true), 59 | [1, 2, 3]); 60 | assert.deepStrictEqual( 61 | sort([3, 1, 2], cmp, Infinity), 62 | [1, 2, 3]); 63 | }); 64 | 65 | it('random', () => { 66 | const rnd = Sequence.random() 67 | .map(r => r * 1e3 | 0); 68 | for (let i = 0; i < 9; ++i) { 69 | const as = rnd.take(Math.random() * i ** 3).extract(); 70 | assert.deepStrictEqual( 71 | sort(as.slice(), cmp, Infinity, true), 72 | as.sort(cmp)); 73 | } 74 | }); 75 | 76 | }); 77 | 78 | }); 79 | -------------------------------------------------------------------------------- /src/monad/sequence/member/static/difference.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { nat } from '../../../sequence.test'; 3 | 4 | describe('Unit: lib/monad/sequence/member/static/difference', () => { 5 | const double = nat.map(n => n * 2); 6 | const triple = nat.map(n => n * 3); 7 | 8 | describe('Sequence.difference', () => { 9 | it('unlimited', () => { 10 | assert.deepStrictEqual( 11 | Sequence.difference(double, triple, (a, b) => a - b).take(7).extract(), 12 | [2, 3, 4, 8, 9, 10, 14]); 13 | assert.deepStrictEqual( 14 | Sequence.difference(double.map(n => -n), triple.map(n => -n), (a, b) => b - a).take(7).extract(), 15 | [2, 3, 4, 8, 9, 10, 14].map(n => -n)); 16 | }); 17 | 18 | it('same', () => { 19 | assert.deepStrictEqual( 20 | Sequence.difference(double, triple, (a, b) => a - b).take(7).extract(), 21 | [2, 3, 4, 8, 9, 10, 14]); 22 | assert.deepStrictEqual( 23 | Sequence.difference(double.map(n => -n), triple.map(n => -n), (a, b) => b - a).take(7).extract(), 24 | [2, 3, 4, 8, 9, 10, 14].map(n => -n)); 25 | }); 26 | 27 | it('mismatch', () => { 28 | assert.deepStrictEqual( 29 | Sequence.difference(double.dropWhile(n => n < 6).takeUntil(n => n === 12), triple, (a, b) => a - b).take(8).extract(), 30 | [0, 3, 8, 9, 10, 15, 18, 21]); 31 | assert.deepStrictEqual( 32 | Sequence.difference(triple, double.dropWhile(n => n < 6).takeUntil(n => n === 12), (a, b) => a - b).take(8).extract(), 33 | [0, 3, 8, 9, 10, 15, 18, 21]); 34 | }); 35 | 36 | it('empty', () => { 37 | assert.deepStrictEqual( 38 | Sequence.difference(nat, Sequence.from([]), (a, b) => a - b).take(3).extract(), 39 | [0, 1, 2]); 40 | assert.deepStrictEqual( 41 | Sequence.difference(Sequence.from([]), nat, (a, b) => a - b).take(3).extract(), 42 | [0, 1, 2]); 43 | assert.deepStrictEqual( 44 | Sequence.difference(Sequence.from([]), Sequence.from([]), () => 0).take(3).extract(), 45 | []); 46 | }); 47 | 48 | }); 49 | 50 | }); 51 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/bind.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { nat } from '../../../sequence.test'; 3 | 4 | describe('Unit: lib/monad/sequence/member/bind', () => { 5 | describe('bind', () => { 6 | it('0', () => { 7 | assert.deepStrictEqual( 8 | nat 9 | .bind(n => new Sequence((m = 0, cons) => m < n ? cons(m, m + 1) : cons())) 10 | .take(0) 11 | .extract(), 12 | []); 13 | }); 14 | 15 | it('1', () => { 16 | assert.deepStrictEqual( 17 | nat 18 | .bind(n => new Sequence((m = 0, cons) => m < n ? cons(m, m + 1) : cons())) 19 | .take(1) 20 | .extract(), 21 | [0]); 22 | }); 23 | 24 | it('1 + 1', () => { 25 | assert.deepStrictEqual( 26 | nat 27 | .bind(n => new Sequence((m = 0, cons) => m < n ? cons(m, m + 1) : cons())) 28 | .take(2) 29 | .extract(), 30 | [0, 0]); 31 | }); 32 | 33 | it('1 + 2', () => { 34 | assert.deepStrictEqual( 35 | nat 36 | .bind(n => new Sequence((m = 0, cons) => m < n ? cons(m, m + 1) : cons())) 37 | .take(3) 38 | .extract(), 39 | [0, 0, 1]); 40 | }); 41 | 42 | it('1 + 2 + 3 + 1', () => { 43 | assert.deepStrictEqual( 44 | nat 45 | .bind(n => new Sequence((m = 0, cons) => m < n ? cons(m, m + 1) : cons())) 46 | .take(7) 47 | .extract(), 48 | [0, 0, 1, 0, 1, 2, 0]); 49 | }); 50 | 51 | it('nothing', () => { 52 | assert.deepStrictEqual( 53 | nat 54 | .take(3) 55 | .bind(() => Sequence.from([])) 56 | .take(Infinity) 57 | .extract(), 58 | []); 59 | }); 60 | 61 | it('combination', () => { 62 | assert.deepStrictEqual( 63 | nat 64 | .take(3) 65 | .bind(n => Sequence.from([n, -n])) 66 | .take(Infinity) 67 | .extract(), 68 | [0, 0, 1, -1, 2, -2]); 69 | }); 70 | 71 | }); 72 | 73 | }); 74 | -------------------------------------------------------------------------------- /src/tlru.ts: -------------------------------------------------------------------------------- 1 | // TLRU: True LRU 2 | // TRC: True Recency-based Cache 3 | 4 | export * from './tlru.clock'; 5 | 6 | /* 7 | 真に最近性に基づく真のLRU。 8 | 最近性には有参照間、無参照間、有無参照間の3つがある。 9 | LRUは有無参照間の最近性を喪失しClockは有参照間の最近性を喪失する。 10 | TLRUはすべての最近性を保持する。 11 | パラメータを調整しやすく用途に合わせてヒット率を上げやすい。 12 | stepパラメータはヒットエントリを重み付けおよび保護しており 13 | demotion=100で重み付けと保護なしの純粋なTLRUを設定できる。 14 | windowパラメータでSLRU同様捕捉可能最小再利用距離を設定できるが 15 | 降格区間内では捕捉可能再利用距離が半減しSLRUより短くなる。 16 | DWCより高速かつ堅牢でアプリケーションのインメモリキャッシュなどの 17 | 極端に変化の大きいアクセスパターンにも適応する。 18 | 19 | */ 20 | 21 | /* 22 | LRUとClockは偽の最近性に基づく誤ったアルゴリズムにより性能が大幅に低下する。 23 | 真の最近性は偽の最近性よりも非常に優れている。 24 | 25 | エントリ間の最近性関係には使用済みと使用済み、使用済みと未使用、未使用と未使用の3種類がある。 26 | ただしLRUとClockは一部の最近性に違反する。真のLRUはすべての最近性を維持することにより 27 | LRUとClockよりも優れた性能を発揮する。 28 | 29 | LRUの根本的誤りは新しいエントリを最近使用されたと見なすことである。実際それがキャッシュ内で 30 | 使用されたことはない。従って新しいエントリは実際に使用されたエントリの後ろに追加する必要がある。 31 | 32 | ``` 33 | Sequence: 1, 2, 3, 3, 2, 4 34 | 35 | LRU 36 | 37 | MRU |4 2 3 1| LRU 38 | Hit |0 1 1 0| 39 | ^ Violation of the recency between used and unused. 40 | 41 | Clock 42 | 43 | N-1 |4 3 2 1| 0 44 | Hit |0 1 1 0| 45 | ^ Violation of the recency between used and used. 46 | 47 | True LRU 48 | 49 | MRU |2 3 4 1| LRU 50 | Hit |1 1 0 0| 51 | ^ ^ ^ Ideal recency(Recency-complete). 52 | ``` 53 | 54 | この最近性はClockですでに使用され普及していることから奇異でも不合理でもないことが証明されて 55 | おりLRUよりClockの方がヒット率が同等または非常に高いことから使用済みエントリ間の最近性より 56 | 未使用エントリとの最近性の方が効果が高く重要であることがわかる。 57 | 58 | またClockはLRUの近似アルゴリズムとして知られているがLRUとClockはこのように異なる種類の最近性 59 | に基づくアルゴリズムであることからClockは実際にはLRUの近似アルゴリズムではなく異なる種類の 60 | 最近性に基づくまったく異なる最近性基準アルゴリズムである。 61 | 62 | |Algorithm|Used-Used|Used-Unused|Unused-Unused| 63 | |:-------:|:-------:|:---------:|:-----------:| 64 | |LRU |✓ | |✓ | 65 | |Clock | |✓ |✓ | 66 | |True LRU |✓ |✓ |✓ | 67 | 68 | 再利用距離と同様に使用済みと未使用の最近性には有限と無限の差があり差を埋める方法には 69 | 様々な方法が考えられこの調整可能性はTrue LRUとClockにのみ存在しLRUには存在しない。 70 | 71 | True LRUにおけるLRUからの大幅な改善はすべてのアルゴリズムの改善の過半が未使用のエントリを 72 | 偶然削除したことによるものを独自の改善として混同および錯覚したものであり各アルゴリズムの 73 | 独自性による改善は小さいか半分に満たないことを示している。True LRUをLRUの代わりに真の 74 | ベースラインとすると他のアルゴリズムは特に汎用性においてあまり魅力的な性能を達成していない。 75 | 76 | */ 77 | -------------------------------------------------------------------------------- /src/monad/sequence.ts: -------------------------------------------------------------------------------- 1 | import './sequence/member/static/resume'; 2 | import './sequence/member/static/from'; 3 | import './sequence/member/static/cycle'; 4 | import './sequence/member/static/random'; 5 | import './sequence/member/static/concat'; 6 | import './sequence/member/static/zip'; 7 | import './sequence/member/static/difference'; 8 | import './sequence/member/static/union'; 9 | import './sequence/member/static/intersect'; 10 | import './sequence/member/static/pure'; 11 | import './sequence/member/static/return'; 12 | import './sequence/member/static/sequence'; 13 | import './sequence/member/static/mempty'; 14 | import './sequence/member/static/mconcat'; 15 | import './sequence/member/static/mappend'; 16 | import './sequence/member/static/mzero'; 17 | import './sequence/member/static/mplus'; 18 | import './sequence/member/instance/extract'; 19 | import './sequence/member/instance/iterate'; 20 | import './sequence/member/instance/memoize'; 21 | import './sequence/member/instance/reduce'; 22 | import './sequence/member/instance/take'; 23 | import './sequence/member/instance/drop'; 24 | import './sequence/member/instance/takeWhile'; 25 | import './sequence/member/instance/dropWhile'; 26 | import './sequence/member/instance/takeUntil'; 27 | import './sequence/member/instance/dropUntil'; 28 | import './sequence/member/instance/sort'; 29 | import './sequence/member/instance/unique'; 30 | import './sequence/member/instance/fmap'; 31 | import './sequence/member/instance/ap'; 32 | import './sequence/member/instance/bind'; 33 | import './sequence/member/instance/join'; 34 | import './sequence/member/instance/mapM'; 35 | import './sequence/member/instance/filterM'; 36 | import './sequence/member/instance/map'; 37 | import './sequence/member/instance/filter'; 38 | import './sequence/member/instance/scanl'; 39 | import './sequence/member/instance/foldr'; 40 | import './sequence/member/instance/group'; 41 | import './sequence/member/instance/inits'; 42 | import './sequence/member/instance/tails'; 43 | import './sequence/member/instance/segs'; 44 | import './sequence/member/instance/subsequences'; 45 | import './sequence/member/instance/permutations'; 46 | 47 | export { Sequence } from './sequence/core'; 48 | -------------------------------------------------------------------------------- /src/list/qlist.ts: -------------------------------------------------------------------------------- 1 | export class List { 2 | public head?: N = undefined; 3 | public last?: N = undefined; 4 | public isEmpty(): boolean { 5 | return this.head === undefined; 6 | } 7 | public unshift(node: N): N { 8 | assert(!node.next); 9 | if (this.head === undefined) { 10 | this.head = this.last = node; 11 | } 12 | else { 13 | node.next = this.head; 14 | this.head = node; 15 | } 16 | return node; 17 | } 18 | public push(node: N): N { 19 | assert(!node.next); 20 | if (this.head === undefined) { 21 | this.head = this.last = node; 22 | } 23 | else { 24 | this.last = this.last!.next = node; 25 | } 26 | return node; 27 | } 28 | public shift(): N | undefined { 29 | assert(this.head); 30 | const head = this.head!; 31 | const tail = head.next; 32 | if (tail === undefined) { 33 | this.head = this.last = undefined; 34 | } 35 | else { 36 | head.next = undefined; 37 | this.head = tail; 38 | } 39 | return head; 40 | } 41 | public clear(): void { 42 | this.head = this.last = undefined; 43 | } 44 | public *[Symbol.iterator](): Iterator { 45 | for (let node = this.head; node !== undefined; node = node!.next) { 46 | yield node; 47 | } 48 | } 49 | public flatMap(f: (node: N) => ArrayLike): T[] { 50 | const acc = []; 51 | for (let node = this.head; node !== undefined; node = node!.next) { 52 | const as = f(node); 53 | switch (as.length) { 54 | case 0: 55 | break; 56 | case 1: 57 | acc.push(as[0]); 58 | break; 59 | default: 60 | for (let len = as.length, i = 0; i < len; ++i) { 61 | acc.push(as[i]); 62 | } 63 | } 64 | } 65 | return acc; 66 | } 67 | public find(f: (node: N) => unknown): N | undefined { 68 | for (let head = this.head, node = head; node;) { 69 | if (f(node)) return node; 70 | node = node.next; 71 | if (node === head) break; 72 | } 73 | } 74 | } 75 | export namespace List { 76 | export class Node { 77 | public next?: this = undefined; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /benchmark/memoize.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { memoize, reduce } from '../src/memoize'; 3 | import { Clock } from '../src/clock'; 4 | import { TLRU } from '../src/tlru'; 5 | import { Cache } from '../src/cache'; 6 | 7 | describe('Benchmark:', function () { 8 | describe('memoize', function () { 9 | for (const exp of [7, 10, 12, 14, 16, 18, 20]) { 10 | const size = 1 << exp; 11 | const mask = size - 1; 12 | 13 | it(`Array ${size.toLocaleString('en')}`, function (done) { 14 | const f = memoize(a => a, []); 15 | let i = 0; 16 | benchmark(`memoize Array ${size.toLocaleString('en')}`, () => f(i = ++i & mask), done); 17 | }); 18 | 19 | it(`Object ${size.toLocaleString('en')}`, function (done) { 20 | const f = memoize(a => a, {}); 21 | let i = 0; 22 | benchmark(`memoize Object ${size.toLocaleString('en')}`, () => f(i = ++i & mask), done); 23 | }); 24 | 25 | it(`Map ${size.toLocaleString('en')}`, function (done) { 26 | const f = memoize(a => a, new Map()); 27 | let i = 0; 28 | benchmark(`memoize Map ${size.toLocaleString('en')}`, () => f(i = ++i & mask), done); 29 | }); 30 | 31 | it(`Clock ${size.toLocaleString('en')}`, function (done) { 32 | const f = memoize(a => a, new Clock(size)); 33 | let i = 0; 34 | benchmark(`memoize Clock ${size.toLocaleString('en')}`, () => f(i = ++i & mask), done); 35 | }); 36 | 37 | it(`TLRU ${size.toLocaleString('en')}`, function (done) { 38 | const f = memoize(a => a, new TLRU(size)); 39 | let i = 0; 40 | benchmark(`memoize TLRU ${size.toLocaleString('en')}`, () => f(i = ++i & mask), done); 41 | }); 42 | 43 | it(`Cache ${size.toLocaleString('en')}`, function (done) { 44 | const f = memoize(a => a, new Cache(size)); 45 | let i = 0; 46 | benchmark(`memoize Cache ${size.toLocaleString('en')}`, () => f(i = ++i & mask), done); 47 | }); 48 | } 49 | }); 50 | 51 | describe('reduce', function () { 52 | it('', function (done) { 53 | const f = reduce(a => a); 54 | benchmark('reduce', () => f(0), done); 55 | }); 56 | }); 57 | 58 | }); 59 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/takeUntil.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { nat } from '../../../sequence.test'; 3 | 4 | describe('Unit: lib/monad/sequence/member/takeUntil', () => { 5 | describe('takeUntil', () => { 6 | it('0 always', () => { 7 | assert.deepStrictEqual( 8 | new Sequence((_ = 0, cons) => cons()) 9 | .takeUntil(() => true) 10 | .extract(), 11 | []); 12 | }); 13 | 14 | it('0 never', () => { 15 | assert.deepStrictEqual( 16 | new Sequence((_ = 0, cons) => cons()) 17 | .takeUntil(() => false) 18 | .extract(), 19 | []); 20 | }); 21 | 22 | it('1 always', () => { 23 | assert.deepStrictEqual( 24 | new Sequence((n = 0, cons) => cons(n)) 25 | .takeUntil(() => true) 26 | .extract(), 27 | [0]); 28 | }); 29 | 30 | it('1 never', () => { 31 | assert.deepStrictEqual( 32 | new Sequence((n = 0, cons) => cons(n)) 33 | .takeUntil(() => false) 34 | .extract(), 35 | [0]); 36 | }); 37 | 38 | it('2 always', () => { 39 | assert.deepStrictEqual( 40 | new Sequence((n = 0, cons) => n < 1 ? cons(n, n + 1) : cons(n)) 41 | .takeUntil(() => true) 42 | .extract(), 43 | [0]); 44 | }); 45 | 46 | it('2 never', () => { 47 | assert.deepStrictEqual( 48 | new Sequence((n = 0, cons) => n < 1 ? cons(n, n + 1) : cons(n)) 49 | .takeUntil(() => false) 50 | .extract(), 51 | [0, 1]); 52 | }); 53 | 54 | it('1', () => { 55 | assert.deepStrictEqual( 56 | nat 57 | .takeUntil(n => n === 0) 58 | .extract(), 59 | [0]); 60 | }); 61 | 62 | it('2', () => { 63 | assert.deepStrictEqual( 64 | nat 65 | .takeUntil(n => n === 1) 66 | .extract(), 67 | [0, 1]); 68 | }); 69 | 70 | it('3', () => { 71 | assert.deepStrictEqual( 72 | nat 73 | .takeUntil(n => n === 2) 74 | .extract(), 75 | [0, 1, 2]); 76 | }); 77 | 78 | }); 79 | 80 | }); 81 | -------------------------------------------------------------------------------- /src/future.ts: -------------------------------------------------------------------------------- 1 | import { AtomicPromise, Internal, internal } from './promise'; 2 | 3 | const state = Symbol('spica/future::state'); 4 | 5 | export class Future extends Promise { 6 | public static get [Symbol.species]() { 7 | return Promise; 8 | } 9 | constructor(private readonly strict: boolean = true) { 10 | let resolve!: (v: T | PromiseLike) => void; 11 | super(r => resolve = r); 12 | this[state] = { 13 | pending: true, 14 | resolve, 15 | }; 16 | } 17 | private readonly [state]: { 18 | pending: boolean, 19 | resolve: (value: T | PromiseLike) => void, 20 | }; 21 | private bind$(value: T | PromiseLike): Promise; 22 | private bind$(this: Future, value?: T | PromiseLike): Promise; 23 | private bind$(value: T | PromiseLike): Promise { 24 | if (this[state].pending) { 25 | this[state].pending = false; 26 | this[state].resolve(value); 27 | } 28 | else if (this.strict) { 29 | throw new Error(`Spica: Future: Cannot rebind the value`); 30 | } 31 | return this; 32 | } 33 | public get bind(): Future['bind$'] { 34 | return value => this.bind$(value!); 35 | } 36 | } 37 | 38 | export interface AtomicFuture extends AtomicPromise { } 39 | export class AtomicFuture implements AtomicPromise { 40 | public readonly [Symbol.toStringTag]: string = 'Promise'; 41 | constructor(private readonly strict: boolean = true) { 42 | } 43 | public readonly [internal] = new Internal(); 44 | private bind$(value: T | PromiseLike): AtomicPromise; 45 | private bind$(this: AtomicFuture, value?: T | PromiseLike): AtomicPromise; 46 | private bind$(value: T | PromiseLike): AtomicPromise { 47 | if (this[internal].isPending()) { 48 | this[internal].resolve(value); 49 | } 50 | else if (this.strict) { 51 | throw new Error(`Spica: AtomicFuture: Cannot rebind the value`); 52 | } 53 | return this; 54 | } 55 | public get bind(): AtomicFuture['bind$'] { 56 | return value => this.bind$(value!); 57 | } 58 | } 59 | AtomicFuture.prototype.then = AtomicPromise.prototype.then; 60 | AtomicFuture.prototype.catch = AtomicPromise.prototype.catch; 61 | AtomicFuture.prototype.finally = AtomicPromise.prototype.finally; 62 | -------------------------------------------------------------------------------- /src/curry.test.ts: -------------------------------------------------------------------------------- 1 | import { curry, uncurry } from './curry'; 2 | 3 | describe('Unit: lib/curry', () => { 4 | describe('curry', () => { 5 | it('1', () => { 6 | assert(curry((a: number) => a)(1) === 1); 7 | }); 8 | 9 | it('2', () => { 10 | assert(curry((a: number, b: number) => a + b)(1)(2) === 3); 11 | assert(curry((a: number, b: number) => a + b)(1, 2) === 3); 12 | }); 13 | 14 | it('3', () => { 15 | assert(curry((a: number, b: number, c: number) => a + b + c)(1)(2)(3) === 6); 16 | assert(curry((a: number, b: number, c: number) => a + b + c)(1, 2)(3) === 6); 17 | assert(curry((a: number, b: number, c: number) => a + b + c)(1)(2, 3) === 6); 18 | assert(curry((a: number, b: number, c: number) => a + b + c)(1, 2, 3) === 6); 19 | }); 20 | 21 | it('extra parameters', () => { 22 | assert.deepStrictEqual([1].map(curry((a: number) => a)), [1]); 23 | assert.deepStrictEqual([1].map(curry((a: number, b: number = NaN) => [a, b])), [[1, 0]]); 24 | }); 25 | 26 | it('recursive', () => { 27 | assert(curry(curry((a: number) => a))(1) === 1); 28 | assert(curry(curry((a: number, b: number) => a + b))(1)(2) === 3); 29 | assert(curry(curry((a: number, b: number) => a + b)(1))(2) === 3); 30 | }); 31 | 32 | it('generic', () => { 33 | assert(curry((a: T, b: U) => a + b)(1)(2) === 3); 34 | assert(curry((a: T, b: U) => a + b)(1, 2) === 3); 35 | }); 36 | 37 | }); 38 | 39 | describe('uncurry', () => { 40 | it('1', () => { 41 | assert.deepStrictEqual( 42 | uncurry((a: number) => [a])(1), 43 | [1]); 44 | }); 45 | 46 | it('2', () => { 47 | assert.deepStrictEqual( 48 | uncurry((a: number) => (b: number) => [a, b])(1, 2), 49 | [1, 2]); 50 | }); 51 | 52 | it('3', () => { 53 | assert.deepStrictEqual( 54 | uncurry((a: number) => (b: number) => (c: number) => [a, b, c])(1, 2, 3), 55 | [1, 2, 3]); 56 | }); 57 | 58 | it('extra parameters', () => { 59 | assert.deepStrictEqual([1].map(uncurry(curry((a: number) => a))), [1]); 60 | assert.deepStrictEqual([1].map(uncurry(curry((a: number, b: number = NaN) => [a, b]))), [[1, 0]]); 61 | }); 62 | 63 | }); 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/dropWhile.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { nat } from '../../../sequence.test'; 3 | 4 | describe('Unit: lib/monad/sequence/member/dropWhile', () => { 5 | describe('dropWhile', () => { 6 | it('0 nothing', () => { 7 | assert.deepStrictEqual( 8 | new Sequence((_ = 0, cons) => cons()) 9 | .dropWhile(() => false) 10 | .extract(), 11 | []); 12 | }); 13 | 14 | it('0 all', () => { 15 | assert.deepStrictEqual( 16 | new Sequence((_ = 0, cons) => cons()) 17 | .dropWhile(() => true) 18 | .extract(), 19 | []); 20 | }); 21 | 22 | it('1 nothing', () => { 23 | assert.deepStrictEqual( 24 | new Sequence((n = 0, cons) => cons(n)) 25 | .dropWhile(() => false) 26 | .extract(), 27 | [0]); 28 | }); 29 | 30 | it('1 all', () => { 31 | assert.deepStrictEqual( 32 | new Sequence((n = 0, cons) => cons(n)) 33 | .dropWhile(() => true) 34 | .extract(), 35 | []); 36 | }); 37 | 38 | it('2 nothing', () => { 39 | assert.deepStrictEqual( 40 | new Sequence((n = 0, cons) => n < 1 ? cons(n, n + 1) : cons(n)) 41 | .dropWhile(() => false) 42 | .extract(), 43 | [0, 1]); 44 | }); 45 | 46 | it('2 all', () => { 47 | assert.deepStrictEqual( 48 | new Sequence((n = 0, cons) => n < 1 ? cons(n, n + 1) : cons(n)) 49 | .dropWhile(() => true) 50 | .extract(), 51 | []); 52 | }); 53 | 54 | it('0', () => { 55 | assert.deepStrictEqual( 56 | nat 57 | .dropWhile(n => n < 0) 58 | .take(3) 59 | .extract(), 60 | [0, 1, 2]); 61 | }); 62 | 63 | it('1', () => { 64 | assert.deepStrictEqual( 65 | nat 66 | .dropWhile(n => n < 1) 67 | .take(3) 68 | .extract(), 69 | [1, 2, 3]); 70 | }); 71 | 72 | it('2', () => { 73 | assert.deepStrictEqual( 74 | nat 75 | .dropWhile(n => n < 2) 76 | .take(3) 77 | .extract(), 78 | [2, 3, 4]); 79 | }); 80 | 81 | }); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/dropUntil.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { nat } from '../../../sequence.test'; 3 | 4 | describe('Unit: lib/monad/sequence/member/dropUntil', () => { 5 | describe('dropUntil', () => { 6 | it('0 always', () => { 7 | assert.deepStrictEqual( 8 | new Sequence((_ = 0, cons) => cons()) 9 | .dropUntil(() => true) 10 | .extract(), 11 | []); 12 | }); 13 | 14 | it('0 never', () => { 15 | assert.deepStrictEqual( 16 | new Sequence((_ = 0, cons) => cons()) 17 | .dropUntil(() => false) 18 | .extract(), 19 | []); 20 | }); 21 | 22 | it('1 always', () => { 23 | assert.deepStrictEqual( 24 | new Sequence((n = 0, cons) => cons(n)) 25 | .dropUntil(() => true) 26 | .extract(), 27 | []); 28 | }); 29 | 30 | it('1 never', () => { 31 | assert.deepStrictEqual( 32 | new Sequence((n = 0, cons) => cons(n)) 33 | .dropUntil(() => false) 34 | .extract(), 35 | [0]); 36 | }); 37 | 38 | it('2 always', () => { 39 | assert.deepStrictEqual( 40 | new Sequence((n = 0, cons) => n < 1 ? cons(n, n + 1) : cons(n)) 41 | .dropUntil(() => true) 42 | .extract(), 43 | []); 44 | }); 45 | 46 | it('2 never', () => { 47 | assert.deepStrictEqual( 48 | new Sequence((n = 0, cons) => n < 1 ? cons(n, n + 1) : cons(n)) 49 | .dropUntil(() => false) 50 | .extract(), 51 | [0, 1]); 52 | }); 53 | 54 | it('1', () => { 55 | assert.deepStrictEqual( 56 | nat 57 | .dropUntil(n => n < 0) 58 | .take(3) 59 | .extract(), 60 | [0, 1, 2]); 61 | }); 62 | 63 | it('2', () => { 64 | assert.deepStrictEqual( 65 | nat 66 | .dropUntil(n => n < 1) 67 | .take(3) 68 | .extract(), 69 | [1, 2, 3]); 70 | }); 71 | 72 | it('3', () => { 73 | assert.deepStrictEqual( 74 | nat 75 | .dropUntil(n => n < 2) 76 | .take(3) 77 | .extract(), 78 | [2, 3, 4]); 79 | }); 80 | 81 | }); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /src/chrono.test.ts: -------------------------------------------------------------------------------- 1 | import { clock } from './chrono'; 2 | import { suppressAsyncException } from './exception'; 3 | 4 | describe('Unit: lib/chrono', function () { 5 | describe('chrono', function () { 6 | it('sequence', async function () { 7 | let cnt = 0; 8 | assert.deepStrictEqual( 9 | await Promise.all([ 10 | clock.then(() => ++cnt), 11 | Promise.resolve().then(() => ++cnt), 12 | clock.then(() => ++cnt), 13 | ]), 14 | [ 15 | 1, 16 | 2, 17 | 3, 18 | ]); 19 | }); 20 | 21 | }); 22 | 23 | describe('clock.next', function () { 24 | it('this', function (done) { 25 | clock.next(function (this: void) { 26 | assert(this === undefined); 27 | done(); 28 | }); 29 | }); 30 | 31 | }); 32 | 33 | describe('clock.now', function () { 34 | it('this', function (done) { 35 | clock.now(function (this: void) { 36 | assert(this === undefined); 37 | done(); 38 | }); 39 | }); 40 | 41 | it('async', function (done) { 42 | let async = false; 43 | let cnt = 0; 44 | for (let i = 0; i < 10; ++i) { 45 | clock.now(() => assert(async === true && ++cnt)); 46 | } 47 | clock.now(() => void assert(cnt === 10 && async === true) || done()); 48 | async = true; 49 | }); 50 | 51 | it('serial', function (done) { 52 | const size = 1e6; 53 | let cnt = 0; 54 | for (let i = 0; i < size; ++i) { 55 | clock.now(() => ++cnt); 56 | } 57 | let interrupt = false; 58 | Promise.resolve().then(() => interrupt = true); 59 | clock.now(() => void assert(cnt === size && interrupt === false) || done()); 60 | }); 61 | 62 | it('recursion', function (done) { 63 | clock.now(() => clock.now(done)); 64 | }); 65 | 66 | it.skip('separation', function (done) { 67 | let cnt = 0; 68 | Promise.resolve().then(() => ++cnt); 69 | clock.now(() => { 70 | clock.now(() => void assert(cnt === 0) || done()); 71 | }); 72 | }); 73 | 74 | it('recovery', suppressAsyncException(function (done) { 75 | for (let i = 0; i < 10; ++i) { 76 | clock.now(() => { throw new Error() }); 77 | } 78 | clock.now(done); 79 | })); 80 | 81 | }); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /benchmark/array.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | 3 | describe('Benchmark:', function () { 4 | describe('unshift', function () { 5 | for (const size of [1, 1e1, 1e2, 1e3]) { 6 | it(`for ${size.toLocaleString('en')}`, function (done) { 7 | const as = Array(size).fill(1); 8 | benchmark(`Array unshift for ${size.toLocaleString('en')}`, () => { 9 | const acc = []; 10 | for (let i = as.length; i-- !== 0;) { 11 | acc.unshift(as[i]); 12 | } 13 | }, done); 14 | }); 15 | 16 | it(`for-of ${size.toLocaleString('en')}`, function (done) { 17 | const as = Array(size).fill(1); 18 | benchmark(`Array unshift for-of ${size.toLocaleString('en')}`, () => { 19 | const acc = []; 20 | for (const a of as) { 21 | acc.unshift(a); 22 | } 23 | }, done); 24 | }); 25 | 26 | it(`spread ${size.toLocaleString('en')}`, function (done) { 27 | const as = Array(size).fill(1); 28 | benchmark(`Array unshift spread ${size.toLocaleString('en')}`, () => { 29 | const acc = []; 30 | acc.unshift(...as); 31 | }, done); 32 | }); 33 | } 34 | }); 35 | 36 | describe('push', function () { 37 | for (const size of [1, 1e1, 1e2, 1e3]) { 38 | it(`for ${size.toLocaleString('en')}`, function (done) { 39 | const as = Array(size).fill(1); 40 | benchmark(`Array push for ${size.toLocaleString('en')}`, () => { 41 | const acc = []; 42 | for (let i = 0; i < as.length; ++i) { 43 | acc.push(as[i]); 44 | } 45 | }, done); 46 | }); 47 | 48 | it(`for-of ${size.toLocaleString('en')}`, function (done) { 49 | const as = Array(size).fill(1); 50 | benchmark(`Array push for-of ${size.toLocaleString('en')}`, () => { 51 | const acc = []; 52 | for (const a of as) { 53 | acc.push(a); 54 | } 55 | }, done); 56 | }); 57 | 58 | it(`spread ${size.toLocaleString('en')}`, function (done) { 59 | const as = Array(size).fill(1); 60 | benchmark(`Array push spread ${size.toLocaleString('en')}`, () => { 61 | const acc = []; 62 | acc.push(...as); 63 | }, done); 64 | }); 65 | } 66 | }); 67 | 68 | }); 69 | -------------------------------------------------------------------------------- /src/curry.ts: -------------------------------------------------------------------------------- 1 | import { shift } from './array'; 2 | 3 | export interface Curried1 { 4 | (a: a): z; 5 | } 6 | export interface Curried2 { 7 | (a: a, b: b): z; 8 | (a: a): Curried1; 9 | } 10 | export interface Curried3 { 11 | (a: a, b: b, c: c): z; 12 | (a: a, b: b): Curried1; 13 | (a: a): Curried2; 14 | } 15 | export interface Curried4 { 16 | (a: a, b: b, c: c, d: d): z; 17 | (a: a, b: b, c: c): Curried1; 18 | (a: a, b: b): Curried2; 19 | (a: a): Curried3; 20 | } 21 | export interface Curried5 { 22 | (a: a, b: b, c: c, d: d, e: e): z; 23 | (a: a, b: b, c: c, d: d): Curried1; 24 | (a: a, b: b, c: c): Curried2; 25 | (a: a, b: b): Curried3; 26 | (a: a): Curried4; 27 | } 28 | export interface Curry { 29 | (f: (a: a) => z): Curried1; 30 | (f: (a: a, b: b) => z): Curried2; 31 | (f: (a: a, b: b, c: c) => z): Curried3; 32 | (f: (a: a, b: b, c: c, d: d) => z): Curried4; 33 | (f: (a: a, b: b, c: c, d: d, e: e) => z): Curried5; 34 | } 35 | 36 | export const curry: Curry = ((f: () => z) => 37 | curry_(f, f.length)); 38 | 39 | function curry_(f: (...xs: unknown[]) => unknown, arity: number, ...xs: unknown[]) { 40 | let g: typeof f; 41 | return xs.length < arity 42 | ? (...ys: unknown[]) => curry_(g ??= xs.length ? f.bind(undefined, ...xs) : f, arity - xs.length, ...ys) 43 | : f(...xs); 44 | } 45 | 46 | interface Uncurry { 47 | (f: (a: a) => (b: b) => (c: c) => (d: d) => (e: e) => z): (a: a, b: b, c: c, d: d, e: e) => z; 48 | (f: (a: a) => (b: b) => (c: c) => (d: d) => z): (a: a, b: b, c: c, d: d) => z; 49 | (f: (a: a) => (b: b) => (c: c) => z): (a: a, b: b, c: c) => z; 50 | (f: (a: a) => (b: b) => z): (a: a, b: b) => z; 51 | (f: (a: a) => z): (a: a) => z; 52 | } 53 | 54 | export const uncurry: Uncurry = (f: (...xs: any[]) => any) => 55 | uncurry_(f); 56 | 57 | function uncurry_(f: (...xs: any[]) => any): any { 58 | const arity = f.length; 59 | return (...xs: any[]) => 60 | arity === 0 || xs.length <= arity 61 | ? f(...xs) 62 | : uncurry_(f(...shift(xs, arity)[0]))(...xs); 63 | } 64 | -------------------------------------------------------------------------------- /src/monad/sequence/member/instance/takeWhile.test.ts: -------------------------------------------------------------------------------- 1 | import { Sequence } from '../../../sequence'; 2 | import { nat } from '../../../sequence.test'; 3 | 4 | describe('Unit: lib/monad/sequence/member/takeWhile', () => { 5 | describe('takeWhile', () => { 6 | it('0 all', () => { 7 | assert.deepStrictEqual( 8 | new Sequence((_ = 0, cons) => cons()) 9 | .takeWhile(() => true) 10 | .extract(), 11 | []); 12 | }); 13 | 14 | it('0 nothing', () => { 15 | assert.deepStrictEqual( 16 | new Sequence((_ = 0, cons) => cons()) 17 | .takeWhile(() => false) 18 | .extract(), 19 | []); 20 | }); 21 | 22 | it('1 all', () => { 23 | assert.deepStrictEqual( 24 | new Sequence((n = 0, cons) => cons(n)) 25 | .takeWhile(() => true) 26 | .extract(), 27 | [0]); 28 | }); 29 | 30 | it('1 nothing', () => { 31 | assert.deepStrictEqual( 32 | new Sequence((n = 0, cons) => cons(n)) 33 | .takeWhile(() => false) 34 | .extract(), 35 | []); 36 | }); 37 | 38 | it('2 all', () => { 39 | assert.deepStrictEqual( 40 | new Sequence((n = 0, cons) => n < 1 ? cons(n, n + 1) : cons(n)) 41 | .takeWhile(() => true) 42 | .extract(), 43 | [0, 1]); 44 | }); 45 | 46 | it('2 nothing', () => { 47 | assert.deepStrictEqual( 48 | new Sequence((n = 0, cons) => n < 1 ? cons(n, n + 1) : cons(n)) 49 | .takeWhile(() => false) 50 | .extract(), 51 | []); 52 | }); 53 | 54 | it('0 take', () => { 55 | assert.deepStrictEqual( 56 | nat 57 | .takeWhile(n => n < 0) 58 | .extract(), 59 | []); 60 | }); 61 | 62 | it('1', () => { 63 | assert.deepStrictEqual( 64 | nat 65 | .takeWhile(n => n < 1) 66 | .extract(), 67 | [0]); 68 | }); 69 | 70 | it('2', () => { 71 | assert.deepStrictEqual( 72 | nat 73 | .takeWhile(n => n < 2) 74 | .extract(), 75 | [0, 1]); 76 | }); 77 | 78 | it('3', () => { 79 | assert.deepStrictEqual( 80 | nat 81 | .takeWhile(n => n < 3) 82 | .extract(), 83 | [0, 1, 2]); 84 | }); 85 | 86 | }); 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /src/ascii.percent.ts: -------------------------------------------------------------------------------- 1 | import { min } from './alias'; 2 | 3 | /* 4 | ASCII文字のパーセントエンコーディングを避ければマルチバイト文字のみのエンコーディングとなるため 5 | 常に2単位以上の圧縮となる。2単位での圧縮率でもデルタ25%、ハフマン33%となるためハフマンを採用。 6 | 7 | */ 8 | 9 | const ASCII = [...Array(256)].reduce((acc, _, i) => acc + String.fromCharCode(i), ''); 10 | 11 | function isHEX(code: number): boolean { 12 | return 0x41 <= code && code < 0x47 13 | || 0x30 <= code && code < 0x3a; 14 | } 15 | 16 | interface Options { 17 | start: number; 18 | next: number; 19 | } 20 | 21 | export function encode(input: string, table: Uint8Array, opts: Options): string { 22 | let output = ''; 23 | let buffer = 0; 24 | let count = 0; 25 | for (let i = opts.next = opts.start; i + 2 < input.length; opts.next = ++i) { 26 | if (input[i] !== '%') break; 27 | const code1 = input.charCodeAt(++i); 28 | if (!isHEX(code1)) break; 29 | const code2 = input.charCodeAt(++i); 30 | if (!isHEX(code2)) break; 31 | const delta1 = table[code1]; 32 | assert(delta1 >>> 4 === 0); 33 | const delta2 = table[code2]; 34 | assert(delta2 >>> 4 === 0); 35 | const hcode = 1 << 8 | delta1 << 4 | delta2; 36 | let hlen = 9; 37 | while (hlen !== 0) { 38 | assert(count < 8); 39 | const cnt = min(hlen, 8 - count); 40 | assert(cnt > 0); 41 | buffer |= (hcode >>> hlen - cnt & (1 << cnt) - 1) << 8 - count - cnt; 42 | assert(buffer >>> 8 === 0); 43 | count += cnt; 44 | assert(count <= 8); 45 | hlen -= cnt; 46 | assert(hlen >= 0); 47 | if (count !== 8) continue; 48 | output += ASCII[buffer]; 49 | buffer = 0; 50 | count = 0; 51 | } 52 | } 53 | if (count !== 0) { 54 | assert(count < 8); 55 | output += ASCII[buffer]; 56 | } 57 | assert(output.length <= input.length); 58 | return output; 59 | } 60 | 61 | export function decode(input: string, table: Uint8Array, opts: Options): string { 62 | let output = ''; 63 | let buffer = 0; 64 | let count = 0; 65 | for (let i = opts.next = opts.start; i < input.length; opts.next = ++i) { 66 | buffer <<= 8; 67 | buffer |= input.charCodeAt(i); 68 | count += 8; 69 | assert(count <= 16); 70 | if ((buffer >>> count - 1 & 1) === 0) break; 71 | if (count < 9) continue; 72 | const delta = buffer >>> count - 9 & 0xff; 73 | output += `%${ASCII[table[delta >>> 4]]}${ASCII[table[delta & 0x0f]]}`; 74 | count -= 9; 75 | } 76 | return output; 77 | } 78 | -------------------------------------------------------------------------------- /src/dict/multimap.ts: -------------------------------------------------------------------------------- 1 | import { IterableDict } from '../dict'; 2 | import { Ring } from '../ring'; 3 | 4 | export class MultiMap implements IterableDict { 5 | constructor( 6 | entries?: Iterable, 7 | ) { 8 | if (entries) for (const { 0: k, 1: v } of entries) { 9 | this.set(k, v); 10 | } 11 | } 12 | private memory = new Map>(); 13 | public get size(): number { 14 | return this.memory.size; 15 | } 16 | public get(key: K): V | undefined { 17 | return this.memory.get(key)?.at(0); 18 | } 19 | public getAll(key: K): Ring | undefined { 20 | return this.memory.get(key); 21 | } 22 | public add(key: K, value: V): boolean { 23 | const vs = new Ring(); 24 | vs.push(value); 25 | this.memory.set(key, vs); 26 | return true; 27 | } 28 | public set(key: K, value: V): this { 29 | const vs = this.memory.get(key); 30 | if (vs === undefined) { 31 | this.add(key, value); 32 | return this; 33 | } 34 | vs.push(value); 35 | return this; 36 | } 37 | public has(key: K, value?: V): boolean { 38 | const vs = this.memory.get(key); 39 | if (!vs?.length) return false; 40 | if (arguments.length < 2) return true; 41 | return vs.includes(value!); 42 | } 43 | public delete(key: K, value?: V): boolean { 44 | if (arguments.length < 2) return this.memory.delete(key); 45 | const vs = this.memory.get(key); 46 | if (!vs?.length) return false; 47 | const i = vs.indexOf(value!); 48 | if (i === -1) return false; 49 | vs.splice(i, 1); 50 | return true; 51 | } 52 | public clear(): void { 53 | this.memory = new Map(); 54 | } 55 | public take(key: K): V | undefined; 56 | public take(key: K, count: number): V[]; 57 | public take(key: K, count?: number): V | undefined | V[] { 58 | const vs = this.memory.get(key); 59 | if (count === undefined) return vs?.shift(); 60 | const acc: V[] = []; 61 | while (vs?.length && count--) { 62 | acc.push(vs.shift()!); 63 | } 64 | return acc; 65 | } 66 | public ref(key: K): Ring { 67 | let vs = this.memory.get(key); 68 | if (vs) return vs; 69 | vs = new Ring(); 70 | this.memory.set(key, vs); 71 | return vs; 72 | } 73 | public *[Symbol.iterator](): Iterator<[K, V], undefined, undefined> { 74 | for (const { 0: k, 1: vs } of this.memory) { 75 | for (let i = 0; i < vs.length; ++i) { 76 | yield [k, vs.at(i)!]; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/list/hlist.test.ts: -------------------------------------------------------------------------------- 1 | import { HList } from './hlist'; 2 | 3 | describe('Unit: lib/hlist', () => { 4 | describe('HList', () => { 5 | it('type', () => { 6 | type HNil = typeof HNil; 7 | const HNil = HList(); 8 | // @ts-expect-error 9 | (): HNil => HList(0); 10 | // @ts-expect-error 11 | (): HList<[0]> => HNil; 12 | }); 13 | 14 | it('HList 0', () => { 15 | const node: HList<[]> = HList(); 16 | assert.deepStrictEqual(node.tuple(), []); 17 | }); 18 | 19 | it('HList 1', () => { 20 | const node: HList<[number]> = HList(0); 21 | assert.deepStrictEqual(node.tuple(), [0]); 22 | }); 23 | 24 | it('HList 2', () => { 25 | const node: HList<[number, string]> = HList(0, ''); 26 | assert.deepStrictEqual(node.tuple(), [0, '']); 27 | }); 28 | 29 | it('add', () => { 30 | const node: HList<[number, string]> = HList().add('').add(0); 31 | assert.deepStrictEqual(node.tuple(), HList(0, '').tuple()); 32 | }); 33 | 34 | it('head', () => { 35 | assert(HList(0).head === 0); 36 | assert(HList(0).add('').head === ''); 37 | }); 38 | 39 | it('tail', () => { 40 | assert.deepStrictEqual( 41 | ((): [] => HList(0).tail.tuple())(), 42 | []); 43 | assert.deepStrictEqual( 44 | ((): [string] => HList(0, '').tail.tuple())(), 45 | ['']); 46 | }); 47 | 48 | it('modify', () => { 49 | assert.deepStrictEqual( 50 | ((): [boolean] => HList(0).modify(n => !n).tuple())(), 51 | [true]); 52 | }); 53 | 54 | it('fold', () => { 55 | assert.deepStrictEqual( 56 | ((): [number[]] => HList([] as number[], 1).fold((ns, n) => ns.concat(n)).tuple())(), 57 | [[1]]); 58 | }); 59 | 60 | it('unfold', () => { 61 | assert.deepStrictEqual( 62 | ((): [boolean, number] => HList(0).unfold(n => !n).tuple())(), 63 | [true, 0]); 64 | }); 65 | 66 | it('tuple', () => { 67 | assert.deepStrictEqual( 68 | ((): [boolean, number, string] => HList(false, 0, '').tuple())(), 69 | [false, 0, '']); 70 | }); 71 | 72 | it('reverse', () => { 73 | assert.deepStrictEqual( 74 | ((): [string, number, boolean] => HList(false, 0, '').reverse())(), 75 | ['', 0, false]); 76 | assert.deepStrictEqual( 77 | ((): [string, number, boolean] => HList(...HList(false, 0, '').reverse()).tuple())(), 78 | ['', 0, false]); 79 | }); 80 | 81 | }); 82 | 83 | }); 84 | -------------------------------------------------------------------------------- /src/throttle.test.ts: -------------------------------------------------------------------------------- 1 | import { throttle, debounce, cothrottle } from './throttle'; 2 | import { wait } from './timer'; 3 | 4 | describe('Unit: lib/throttle', () => { 5 | describe('throttle', () => { 6 | it('', (done) => { 7 | let step = 0; 8 | const call = throttle(100, (last, buf) => { 9 | switch (step) { 10 | case 1: 11 | assert(count === 2); 12 | assert(last === 2); 13 | assert.deepStrictEqual(buf, [1, 2]); 14 | step = 2; 15 | call(++count); 16 | break; 17 | case 2: 18 | assert(count === 3); 19 | assert(last === 3); 20 | assert.deepStrictEqual(buf, [3]); 21 | done(); 22 | break; 23 | default: 24 | throw step; 25 | } 26 | return false; 27 | }, 3); 28 | let count = 0; 29 | step = 1; 30 | call(++count); 31 | call(++count); 32 | }); 33 | }); 34 | 35 | describe('debounce', () => { 36 | it('', (done) => { 37 | let step = 0; 38 | const call = debounce(100, (last, buf) => { 39 | switch (step) { 40 | case 1: 41 | assert(count === 3); 42 | assert(last === 3); 43 | assert.deepStrictEqual(buf, [1, 2, 3]); 44 | done(); 45 | break; 46 | default: 47 | throw step; 48 | } 49 | return false; 50 | }, 3); 51 | let count = 0; 52 | step = 1; 53 | setTimeout(() => call(++count), 100); 54 | setTimeout(() => call(++count), 200); 55 | call(++count); 56 | }); 57 | }); 58 | 59 | if (!navigator.userAgent.includes('Chrome')) return; 60 | 61 | describe('cothrottle', function () { 62 | it('', async () => { 63 | let since = Date.now(); 64 | for await (const count of cothrottle(async function* (count = 0) { 65 | since = Date.now(); 66 | await wait(100); 67 | yield ++count; 68 | }, 200, () => wait(150))()) { 69 | switch (count) { 70 | case 1: 71 | assert(Date.now() - since >= 100); 72 | continue; 73 | case 2: 74 | assert(Date.now() - since >= 100); 75 | continue; 76 | case 3: 77 | assert(Date.now() - since >= 350); 78 | continue; 79 | case 4: 80 | assert(Date.now() - since >= 100); 81 | return; 82 | } 83 | } 84 | }); 85 | }); 86 | 87 | }); 88 | -------------------------------------------------------------------------------- /src/timer.ts: -------------------------------------------------------------------------------- 1 | import { List } from './list'; 2 | import { clock } from './chrono'; 3 | import { singleton, noop } from './function'; 4 | 5 | interface Timer { 6 | (timeout: number, handler: () => T, unhandler?: (result: Awaited) => void): () => void; 7 | group(): GroupTimer; 8 | } 9 | interface GroupTimer { 10 | (timeout: number, handler: () => T, unhandler?: (result: Awaited) => void): () => void; 11 | clear(): void; 12 | } 13 | 14 | class Node extends List.Node { 15 | constructor( 16 | public value: T, 17 | ) { 18 | super(); 19 | } 20 | } 21 | 22 | export const setTimer = template(false); 23 | export const setRepeatTimer = template(true); 24 | 25 | function template(repeat: boolean): Timer; 26 | function template(repeat: boolean, cancellers: List void>>): GroupTimer; 27 | function template(repeat: boolean, cancellers?: List void>>): Timer | GroupTimer { 28 | const timer = ((timeout, handler, unhandler?): () => void => { 29 | let params: [Awaited>]; 30 | let id = setTimeout(async function loop() { 31 | params = [await handler()]; 32 | if (!repeat) return; 33 | id = setTimeout(loop, timeout); 34 | }, timeout); 35 | const cancel = singleton(() => { 36 | clearTimeout(id); 37 | cancellers && node && (node.next || node.prev || node === cancellers.head) && cancellers.delete(node); 38 | params && unhandler?.(params[0]); 39 | }); 40 | const node = cancellers?.push(new Node(cancel)); 41 | return cancel; 42 | }) as Timer & GroupTimer; 43 | if (!cancellers) { 44 | timer.group = () => template(repeat, new List()); 45 | } 46 | else { 47 | timer.clear = () => { 48 | while (cancellers.length !== 0) { 49 | cancellers.shift()!.value(); 50 | } 51 | }; 52 | } 53 | return timer; 54 | } 55 | 56 | export function captureTimers(test: (done: (err?: unknown) => void) => unknown): (done: (err?: unknown) => unknown) => void { 57 | const start = setTimeout(noop) as any; 58 | clearTimeout(start); 59 | if (typeof start !== 'number') throw new Error('Timer ID must be a number'); 60 | return done => test(err => { 61 | // Must get the ID before calling done. 62 | const end = setTimeout(noop) as unknown as number; 63 | done(err); 64 | clearTimeout(end); 65 | for (let i = start; i < end; ++i) { 66 | clearTimeout(i); 67 | } 68 | }); 69 | } 70 | 71 | export function wait(ms: number): Promise { 72 | assert(ms >= 0); 73 | return ms === 0 74 | ? clock 75 | : new Promise(resolve => void setTimeout(resolve, ms)); 76 | } 77 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ['**', '!gh-pages'] 6 | pull_request: 7 | branches: ['**', '!gh-pages'] 8 | create: 9 | tags: 'v[0-9]*' 10 | 11 | jobs: 12 | 13 | test: 14 | name: Test on ${{ matrix.os }} 15 | strategy: 16 | matrix: { os: [ubuntu-latest] } 17 | runs-on: ${{ matrix.os }} 18 | steps: 19 | - uses: actions/checkout@v2 20 | - uses: actions/cache@v2 21 | with: 22 | path: ~/.npm 23 | key: ${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json') }} 24 | restore-keys: ${{ runner.os }}- 25 | - uses: actions/setup-node@v2 26 | with: 27 | node-version: '16' 28 | - run: | 29 | npm install 30 | xvfb-run --auto-servernum npm test 31 | npm run lint 32 | 33 | deploy: 34 | name: Deploy on ${{ matrix.os }} 35 | needs: test 36 | if: | 37 | github.event_name == 'create' && 38 | startsWith(github.ref, 'refs/tags/') 39 | strategy: 40 | matrix: { os: [ubuntu-latest] } 41 | runs-on: ${{ matrix.os }} 42 | steps: 43 | - uses: actions/checkout@v2 44 | - uses: actions/cache@v2 45 | with: 46 | path: ~/.npm 47 | key: ${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json') }} 48 | restore-keys: ${{ runner.os }}- 49 | - uses: actions/setup-node@v2 50 | with: 51 | node-version: '16' 52 | registry-url: 'https://registry.npmjs.org' 53 | - run: | 54 | npm install 55 | #npm run dist 56 | sed -i 's/"private":\ true/"private":\ false/' package.json 57 | sed -i -E 's/^dist\/?.*$//' .gitignore 58 | mv src/* . 59 | - run: npm publish 60 | env: 61 | NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} 62 | 63 | benchmark: 64 | name: Benchmark on ${{ matrix.os }} 65 | needs: test 66 | if: | 67 | github.event_name == 'push' || 68 | github.event_name == 'pull_request' 69 | strategy: 70 | matrix: { os: [ubuntu-latest] } 71 | runs-on: ${{ matrix.os }} 72 | steps: 73 | - uses: actions/checkout@v2 74 | - uses: actions/cache@v2 75 | with: 76 | path: ~/.npm 77 | key: ${{ runner.os }}-${{ hashFiles('package.json', 'package-lock.json') }} 78 | restore-keys: ${{ runner.os }}- 79 | - uses: actions/setup-node@v2 80 | with: 81 | node-version: '16' 82 | - run: | 83 | npx envinfo -yes 84 | npm install 85 | xvfb-run --auto-servernum npm run bench 86 | -------------------------------------------------------------------------------- /benchmark/ascii.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { encode as encodeDelta, decode as decodeDelta } from '../src/ascii.delta'; 3 | import { encode as encodeHuffm, decode as decodeHuffm } from '../src/ascii.huffman'; 4 | import { encode as encodeHPACK, decode as decodeHPACK } from '../src/ascii.hpack'; 5 | 6 | describe('Benchmark:', function () { 7 | describe('ascii', function () { 8 | const word = 'ascii'; 9 | const text = 'Hello, world.'; 10 | 11 | it('encode word delta', function (done) { 12 | const str = word; 13 | benchmark('ascii encode word delta', () => encodeDelta(str), done); 14 | }); 15 | 16 | it('encode word huffm', function (done) { 17 | const str = word; 18 | benchmark('ascii encode word huffm', () => encodeHuffm(str), done); 19 | }); 20 | 21 | it('encode word hpack', function (done) { 22 | const str = word; 23 | benchmark('ascii encode word hpack', () => encodeHPACK(str), done); 24 | }); 25 | 26 | it('encode text delta', function (done) { 27 | const str = text; 28 | benchmark('ascii encode text delta', () => encodeDelta(str), done); 29 | }); 30 | 31 | it('encode text huffm', function (done) { 32 | const str = text; 33 | benchmark('ascii encode text huffm', () => encodeHuffm(str), done); 34 | }); 35 | 36 | it('encode text hpack', function (done) { 37 | const str = text; 38 | benchmark('ascii encode text hpack', () => encodeHPACK(str), done); 39 | }); 40 | 41 | it('decode word delta', function (done) { 42 | const str = encodeDelta(word); 43 | benchmark('ascii decode word delta', () => decodeDelta(str), done); 44 | }); 45 | 46 | it('decode word huffm', function (done) { 47 | const str = encodeHuffm(word); 48 | benchmark('ascii decode word huffm', () => decodeHuffm(str), done); 49 | }); 50 | 51 | it('decode word hpack', function (done) { 52 | const str = encodeHPACK(word); 53 | benchmark('ascii decode word hpack', () => decodeHPACK(str), done); 54 | }); 55 | 56 | it('decode text delta', function (done) { 57 | const str = encodeDelta(text); 58 | benchmark('ascii decode text delta', () => decodeDelta(str), done); 59 | }); 60 | 61 | it('decode text huffm', function (done) { 62 | const str = encodeHuffm(text); 63 | benchmark('ascii decode text huffm', () => decodeHuffm(str), done); 64 | }); 65 | 66 | it('decode text hpack', function (done) { 67 | const str = encodeHPACK(text); 68 | benchmark('ascii decode text hpack', () => decodeHPACK(str), done); 69 | }); 70 | 71 | }); 72 | 73 | }); 74 | -------------------------------------------------------------------------------- /benchmark/sequence.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from './benchmark'; 2 | import { Sequence } from '../src/sequence'; 3 | 4 | describe('Benchmark:', function () { 5 | describe('Sequence', function () { 6 | for (const size of [1, 1e1, 1e2, 1e3]) { 7 | it(`Sequence take arr ${size.toLocaleString('en')}`, function (done) { 8 | const arr = Array(size).fill(0); 9 | benchmark(`Sequence take arr ${size.toLocaleString('en')}`, () => 10 | arr.slice(0, size), done); 11 | }); 12 | 13 | it(`Sequence take iter ${size.toLocaleString('en')}`, function (done) { 14 | const seq = Sequence.from(Array(size)); 15 | benchmark(`Sequence take iter ${size.toLocaleString('en')}`, () => 16 | seq.extract(), done); 17 | }); 18 | 19 | it(`Sequence take seq ${size.toLocaleString('en')}`, function (done) { 20 | const seq = new Sequence((n = 0, cons) => cons(n, n + 1)).take(size); 21 | benchmark(`Sequence take seq ${size.toLocaleString('en')}`, () => 22 | seq.extract(), done); 23 | }); 24 | 25 | it(`Sequence take mem ${size.toLocaleString('en')}`, function (done) { 26 | const seq = new Sequence((n = 0, cons) => cons(n, n + 1)).take(size).memoize(); 27 | seq.extract(); 28 | benchmark(`Sequence take mem ${size.toLocaleString('en')}`, () => 29 | seq.extract(), done); 30 | }); 31 | } 32 | 33 | for (const size of [1, 1e1, 1e2, 1e3]) { 34 | it(`Sequence map filter arr ${size.toLocaleString('en')}`, function (done) { 35 | const arr = Array(size).fill(0); 36 | const f = (n: T) => n; 37 | const g = () => true; 38 | benchmark(`Sequence map filter arr ${size.toLocaleString('en')}`, () => 39 | arr.slice(0, size).map(f).filter(g), done); 40 | }); 41 | 42 | it(`Sequence map filter iter ${size.toLocaleString('en')}`, function (done) { 43 | const seq = Sequence.from(Array(size)) 44 | .map(n => n) 45 | .filter(() => true) 46 | .take(size); 47 | benchmark(`Sequence map filter iter ${size.toLocaleString('en')}`, () => 48 | seq.extract(), done); 49 | }); 50 | 51 | it(`Sequence map filter seq ${size.toLocaleString('en')}`, function (done) { 52 | const seq = new Sequence((n = 0, cons) => cons(n, n + 1)) 53 | .map(n => n) 54 | .filter(() => true) 55 | .take(size); 56 | benchmark(`Sequence map filter seq ${size.toLocaleString('en')}`, () => 57 | seq.extract(), done); 58 | }); 59 | } 60 | 61 | }); 62 | 63 | }); 64 | -------------------------------------------------------------------------------- /src/queue.test.ts: -------------------------------------------------------------------------------- 1 | import { Queue } from './queue'; 2 | 3 | describe('Unit: lib/queue', () => { 4 | describe('Queue', () => { 5 | it('push/pop', () => { 6 | const queue = new Queue(); 7 | assert(queue.length === 0); 8 | assert(queue.peek() === undefined); 9 | assert(queue.pop() === undefined); 10 | assert(queue.length === 0); 11 | assert(queue.push(0) === undefined); 12 | assert(queue.length === 1); 13 | assert(queue.peek() === 0); 14 | assert(queue.pop() === 0); 15 | assert(queue.length === 0); 16 | assert(queue.pop() === undefined); 17 | assert(queue.length === 0); 18 | assert(queue.push(0) === undefined); 19 | assert(queue.length === 1); 20 | assert(queue.push(1) === undefined); 21 | assert(queue.length === 2); 22 | assert(queue.peek() === 0); 23 | assert(queue.pop() === 0); 24 | assert(queue.length === 1); 25 | assert(queue.push(2) === undefined); 26 | assert(queue.length === 2); 27 | assert(queue.push(3) === undefined); 28 | assert(queue.length === 3); 29 | assert(queue.pop() === 1); 30 | assert(queue.length === 2); 31 | assert(queue.pop() === 2); 32 | assert(queue.length === 1); 33 | assert(queue.pop() === 3); 34 | assert(queue.length === 0); 35 | assert(queue.pop() === undefined); 36 | assert(queue.length === 0); 37 | }); 38 | 39 | it('at', () => { 40 | const queue = new Queue(); 41 | assert(queue.peek() === undefined); 42 | assert(queue.peek(-1) === undefined); 43 | queue.push(1); 44 | assert(queue.peek() === 1); 45 | assert(queue.peek(-1) === 1); 46 | queue.push(2); 47 | assert(queue.peek() === 1); 48 | assert(queue.peek(-1) === 2); 49 | queue.pop(); 50 | assert(queue.peek() === 2); 51 | assert(queue.peek(-1) === 2); 52 | queue.push(3); 53 | assert(queue.peek() === 2); 54 | assert(queue.peek(-1) === 3); 55 | }); 56 | 57 | it('verify', () => { 58 | for (const size of [ 59 | 16 + 2048 * 0, 60 | 16 + 2048 * 1, 61 | 16 + 2048 * 2, 62 | 16 + 2048 * 3, 63 | ]) { 64 | const queue = new Queue(); 65 | for (let i = -3; i < 4; ++i) { 66 | const len = size + i; 67 | for (let i = 0; i < len; ++i) { 68 | queue.push(queue.length); 69 | } 70 | assert(queue.length === len); 71 | for (let i = 0; i < len; ++i) { 72 | assert(queue.pop() === len - queue.length - 1); 73 | } 74 | assert(queue.length === 0); 75 | } 76 | } 77 | }); 78 | 79 | }); 80 | 81 | }); 82 | -------------------------------------------------------------------------------- /src/dict/datamap.ts: -------------------------------------------------------------------------------- 1 | import { IterableDict } from '../dict'; 2 | import { type } from '../type'; 3 | import { memoize } from '../memoize'; 4 | 5 | export class DataMap implements IterableDict { 6 | constructor( 7 | entries?: Iterable, 8 | private readonly indentify: (key: K) => unknown = stringify, 9 | ) { 10 | if (entries) for (const { 0: k, 1: v } of entries) { 11 | this.set(k, v); 12 | } 13 | } 14 | private readonly memory = new Map(); 15 | public get size(): number { 16 | return this.memory.size; 17 | } 18 | public get(key: K): V | undefined { 19 | return this.memory.get(this.indentify(key))?.[1]; 20 | } 21 | public set(key: K, value: V): this { 22 | this.memory.set(this.indentify(key), [key, value]); 23 | return this; 24 | } 25 | public has(key: K): boolean { 26 | return this.memory.has(this.indentify(key)); 27 | } 28 | public delete(key: K): boolean { 29 | return this.memory.delete(this.indentify(key)); 30 | } 31 | public clear(): void { 32 | return this.memory.clear(); 33 | } 34 | public [Symbol.iterator](): Iterator<[K, V], undefined, undefined> { 35 | return this.memory.values(); 36 | } 37 | } 38 | 39 | function stringify(target: any): string { 40 | const t = type(target); 41 | switch (t) { 42 | case 'undefined': 43 | case 'null': 44 | return `0:${target}`; 45 | case 'boolean': 46 | return `0:${target}`; 47 | case 'number': 48 | return `0:${target}`; 49 | case 'bigint': 50 | return `0:${target}n`; 51 | case 'string': 52 | return `1:${escape(target)}`; 53 | case 'symbol': 54 | return `2:${escape(target.toString())}`; 55 | case 'Function': 56 | return `7:${escape(target)}`; 57 | case 'Array': 58 | return `8:${stringifyArray(target)}`; 59 | case 'Object': 60 | return `8:${stringifyObject(target)}`; 61 | default: 62 | return `9:${escape(t)}(${identify(target)})`; 63 | } 64 | } 65 | 66 | function escape(str: string): string { 67 | return str.indexOf('\n') > -1 68 | ? str.replace(/\n/g, '%0A') 69 | : str; 70 | } 71 | 72 | function stringifyArray(arr: unknown[]): string { 73 | assert(Array.isArray(arr)); 74 | let acc = ''; 75 | for (let i = 0; i < arr.length; ++i) { 76 | acc += `${stringify(arr[i])},\n`; 77 | } 78 | return `[\n${acc}]`; 79 | } 80 | 81 | function stringifyObject(obj: object): string { 82 | let acc = ''; 83 | for (const k in obj) { 84 | acc += `${stringify(k)}: ${stringify(obj[k])},\n`; 85 | } 86 | return `{\n${acc}}`; 87 | } 88 | 89 | const identify = (counter => memoize(() => ++counter, new WeakMap()))(0); 90 | --------------------------------------------------------------------------------