parent
5ce782aa0f
commit
1c70da5f3b
@ -1,7 +0,0 @@ |
|||||||
module.exports = { |
|
||||||
testEnvironment: "node", |
|
||||||
transform: { |
|
||||||
"^.+.tsx?$": ["ts-jest",{}], |
|
||||||
}, |
|
||||||
|
|
||||||
}; |
|
||||||
@ -0,0 +1,9 @@ |
|||||||
|
import type { Config } from 'jest'; |
||||||
|
|
||||||
|
export default { |
||||||
|
testEnvironment: "node", |
||||||
|
transform: { |
||||||
|
"^.+.tsx?$": ["ts-jest", { tsconfig: 'test/tsconfig.json' }], |
||||||
|
}, |
||||||
|
setupFilesAfterEnv: ["./test/util.ts"], |
||||||
|
} as Config; |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
import { Lazy, _ } from './core'; |
||||||
|
|
||||||
|
export type Bool = <T>(t: Lazy<T>) => (f: Lazy<T>) => Lazy<T>; |
||||||
|
|
||||||
|
export const True: Lazy<Bool> |
||||||
|
= _(t => _ => t) as Lazy<Bool>; |
||||||
|
|
||||||
|
export const False: Lazy<Bool> |
||||||
|
= _(_ => f => f); |
||||||
|
|
||||||
|
export const iif: <T>(b: Lazy<Bool>) => (t: Lazy<T>) => (f: Lazy<T>) => Lazy<T> |
||||||
|
= b => t => f => b.then(b => b(t)(f)); |
||||||
|
|
||||||
|
export const not: (b: Lazy<Bool>) => Lazy<Bool> |
||||||
|
= b => b.then(b => b(False)(True)); |
||||||
|
|
||||||
|
export const and: (l: Lazy<Bool>) => (r: Lazy<Bool>) => Lazy<Bool> |
||||||
|
= l => r => l.then(l => l(r)(False)); |
||||||
|
|
||||||
|
export const or: (l: Lazy<Bool>) => (r: Lazy<Bool>) => Lazy<Bool> |
||||||
|
= l => r => l.then(l => l(True)(r)); |
||||||
|
|
||||||
|
export const xor: (l: Lazy<Bool>) => (r: Lazy<Bool>) => Lazy<Bool> |
||||||
|
= l => r => l.then(l => l(not(r))(r)); |
||||||
|
|
||||||
|
export const fromBoolean: (b: boolean) => Lazy<Bool> |
||||||
|
= b => b ? True : False; |
||||||
|
|
||||||
|
export const toBoolean: (b: Lazy<Bool>) => Lazy<boolean> |
||||||
|
= b => b.then(b => b(_(true))(_(false))); |
||||||
@ -1 +1,44 @@ |
|||||||
export const id = <T>(v: T) => v; |
export class Lazy<T> { |
||||||
|
protected _value?: T; |
||||||
|
|
||||||
|
constructor(private _eval: () => T) { } |
||||||
|
|
||||||
|
public evaluated: boolean = false; |
||||||
|
|
||||||
|
public get value(): T { |
||||||
|
if (this.evaluated) { |
||||||
|
return this._value!; |
||||||
|
} |
||||||
|
this.evaluated = true; |
||||||
|
return this._value = this._eval(); |
||||||
|
} |
||||||
|
|
||||||
|
public then<R>(f: (v: T) => Lazy<R>): Lazy<R> { |
||||||
|
return new Lazy(() => f(this.value).value); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type Choice = <T>(t: Lazy<T>) => (f: Lazy<T>) => Lazy<T>; |
||||||
|
|
||||||
|
export class LazyChoice extends Lazy<Choice> { |
||||||
|
public call<T>(l: LazyChoice): (r: LazyChoice) => LazyChoice; |
||||||
|
public call<T>(l: Lazy<T>): (r: Lazy<T>) => Lazy<T>; |
||||||
|
public call<T>(l: Lazy<T> | LazyChoice): (r: Lazy<T> | LazyChoice) => Lazy<T> | LazyChoice { |
||||||
|
return l instanceof LazyChoice |
||||||
|
? (r: LazyChoice) => new LazyChoice(() => this.value(l)(r).value) |
||||||
|
: (r: Lazy<T>) => this.then(c => c(l)(r)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
export const _ = <T>(v: T): Lazy<T> => new Lazy(() => v); |
||||||
|
export const $ = <T>(v: Lazy<T>): T => v.value; |
||||||
|
|
||||||
|
export const undef: Lazy<any> = new Lazy(() => { |
||||||
|
throw new Error('undefined'); |
||||||
|
}); |
||||||
|
|
||||||
|
export const id: <T>(v: Lazy<T>) => Lazy<T> |
||||||
|
= v => v; |
||||||
|
export const cnst: <T> (v: Lazy<T>) => (_: Lazy<unknown>) => Lazy<T> |
||||||
|
= v => _ => v; |
||||||
|
|||||||
@ -0,0 +1,4 @@ |
|||||||
|
export * from './core'; |
||||||
|
export * from './bool'; |
||||||
|
export * from './num'; |
||||||
|
export * from './list'; |
||||||
@ -0,0 +1,50 @@ |
|||||||
|
import { Lazy, _, cnst, id, undef } from './core'; |
||||||
|
import { fromNumber, Num, toNumber } from './num'; |
||||||
|
import { Bool, iif } from './bool'; |
||||||
|
|
||||||
|
export type List<T> = <R>(z: Lazy<R>) => (f: (x: Lazy<T>) => (xs: Lazy<List<T>>) => Lazy<R>) => Lazy<R>; |
||||||
|
|
||||||
|
export const Nil: Lazy<List<any>> |
||||||
|
= _<List<any>>(z => _ => z); |
||||||
|
|
||||||
|
export const Cons: <T>(x: Lazy<T>) => (xs: Lazy<List<T>>) => Lazy<List<T>> |
||||||
|
= <T>(x: Lazy<T>) => (xs: Lazy<List<T>>) => _<List<T>>(_ => f => f(x)(xs)); |
||||||
|
|
||||||
|
export const cons: <T>(x: Lazy<T>) => (xs: Lazy<List<T>>) => Lazy<List<T>> |
||||||
|
= Cons; |
||||||
|
|
||||||
|
export const head: <T>(xs: Lazy<List<T>>) => Lazy<T> |
||||||
|
= xs => xs.then(xs => xs(undef)(cnst)); |
||||||
|
|
||||||
|
export const tail: <T>(xs: Lazy<List<T>>) => Lazy<List<T>> |
||||||
|
= xs => xs.then(xs => xs(undef)(_ => id)); |
||||||
|
|
||||||
|
export const foldl: <T, R>(f: (a: Lazy<R>) => (x: Lazy<T>) => Lazy<R>) => (z: Lazy<R>) => (xs: Lazy<List<T>>) => Lazy<R> |
||||||
|
= f => z => xs => xs.then(xs => xs(z)(x => xs => foldl(f)(f(z)(x))(xs))); |
||||||
|
|
||||||
|
export const foldlz: <T>(f: (a: Lazy<T>) => (x: Lazy<T>) => Lazy<T>) => (xs: Lazy<List<T>>) => Lazy<T> |
||||||
|
= f => xs => xs.then(xs => xs(undef)(foldl(f))); |
||||||
|
|
||||||
|
export const foldr: <T, R>(f: (x: Lazy<T>) => (a: Lazy<R>) => Lazy<R>) => (z: Lazy<R>) => (xs: Lazy<List<T>>) => Lazy<R> |
||||||
|
= f => z => xs => xs.then(xs => xs(z)(x => xs => f(x)(foldr(f)(z)(xs)))); |
||||||
|
|
||||||
|
export const foldrz: <T>(f: (x: Lazy<T>) => (a: Lazy<T>) => Lazy<T>) => (xs: Lazy<List<T>>) => Lazy<T> |
||||||
|
= _ => _ => undef; |
||||||
|
|
||||||
|
export const map: <T, R>(f: (x: Lazy<T>) => Lazy<R>) => (xs: Lazy<List<T>>) => Lazy<List<R>> |
||||||
|
= f => xs => xs.then(xs => xs(Nil)(x => xs => Cons(f(x))(map(f)(xs)))); |
||||||
|
|
||||||
|
export const filter: <T>(f: (x: Lazy<T>) => Lazy<Bool>) => (xs: Lazy<List<T>>) => Lazy<List<T>> |
||||||
|
= <T>(f: (x: Lazy<T>) => Lazy<Bool>) => (xs: Lazy<List<T>>) => xs.then(xs => xs(Nil)(x => xs => iif<List<T>>(f(x))(Cons(x)(filter(f)(xs)))(filter(f)(xs)))); |
||||||
|
|
||||||
|
export const fromArray: <T>(xs: T[]) => Lazy<List<T>> |
||||||
|
= xs => xs.length === 0 ? Nil : new Lazy(() => Cons(_(xs[0]))(fromArray(xs.slice(1))).value); |
||||||
|
|
||||||
|
export const fromNumberArray: (xs: number[]) => Lazy<List<Num>> |
||||||
|
= xs => xs.length === 0 ? Nil : new Lazy(() => Cons(fromNumber(xs[0]))(fromNumberArray(xs.slice(1))).value); |
||||||
|
|
||||||
|
export const toArray: <T>(xs: Lazy<List<T>>) => Lazy<T[]> |
||||||
|
= <T>(xs: Lazy<List<T>>) => xs.then(xs => xs(_([] as T[]))(x => xs => _([x.value, ...toArray(xs).value]))); |
||||||
|
|
||||||
|
export const toNumberArray: (xs: Lazy<List<Num>>) => Lazy<number[]> |
||||||
|
= xs => xs.then(xs => xs(_([] as number[]))(x => xs => _([toNumber(x).value, ...toNumberArray(xs).value]))); |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
import { Bool, not, True } from './bool'; |
||||||
|
import { Lazy, _, id } from './core'; |
||||||
|
|
||||||
|
export type Num = <T>(z: Lazy<T>) => (n: (p: Lazy<Num>) => Lazy<T>) => Lazy<T>; |
||||||
|
|
||||||
|
export const Zero: Lazy<Num> |
||||||
|
= _<Num>(z => _ => z); |
||||||
|
|
||||||
|
export const Succ: (p: Lazy<Num>) => Lazy<Num> |
||||||
|
= p => _(_ => n => n(p)); |
||||||
|
|
||||||
|
export const ifz: <T>(n: Lazy<Num>) => (t: Lazy<T>) => (f: Lazy<T>) => Lazy<T> |
||||||
|
= n => t => f => n.then(n => n(t)(_ => f)); |
||||||
|
|
||||||
|
export const succ: (n: Lazy<Num>) => Lazy<Num> |
||||||
|
= Succ; |
||||||
|
|
||||||
|
export const pred: (n: Lazy<Num>) => Lazy<Num> |
||||||
|
= n => n.then(n => n(Zero)(id)); |
||||||
|
|
||||||
|
export const even: (n: Lazy<Num>) => Lazy<Bool> |
||||||
|
= n => n.then(n => n(True)(odd)); |
||||||
|
|
||||||
|
export const odd: (n: Lazy<Num>) => Lazy<Bool> |
||||||
|
= n => not(even(n)); |
||||||
|
|
||||||
|
export const sum: (l: Lazy<Num>) => (r: Lazy<Num>) => Lazy<Num> |
||||||
|
= l => r => r.then(r => r(l)(sum(Succ(l)))); |
||||||
|
|
||||||
|
export const sub: (l: Lazy<Num>) => (r: Lazy<Num>) => Lazy<Num> |
||||||
|
= l => r => r.then(r => r(l)(pr => l.then(l => l(Zero)(pl => sub(pl)(pr))))); |
||||||
|
|
||||||
|
export const mul: (l: Lazy<Num>) => (r: Lazy<Num>) => Lazy<Num> |
||||||
|
= l => r => l.then(l => l(Zero)(pl => sum(r)(mul(pl)(r)))); |
||||||
|
|
||||||
|
export const fromNumber: (n: number) => Lazy<Num> |
||||||
|
= n => !n ? Zero : new Lazy(() => Succ(fromNumber(n - 1)).value); |
||||||
|
|
||||||
|
export const toNumber: (n: Lazy<Num>) => Lazy<number> |
||||||
|
= n => n.then(n => n(_(0))(p => toNumber(p).then(p => _(1 + p)))); |
||||||
@ -0,0 +1,118 @@ |
|||||||
|
import { describe, expect, it } from '@jest/globals'; |
||||||
|
import { _ } from '../src/core'; |
||||||
|
import { True, False, iif, and, not, or, xor, toBoolean, fromBoolean } from '../src/bool'; |
||||||
|
|
||||||
|
describe('Bool', () => { |
||||||
|
describe('toBoolean', () => { |
||||||
|
it('converts True', () => { |
||||||
|
expect(toBoolean).toDeferEvaluationOf(True); |
||||||
|
expect(toBoolean(True)).toEvaluateTo(true); |
||||||
|
}); |
||||||
|
|
||||||
|
it('converts False', () => { |
||||||
|
expect(toBoolean).toDeferEvaluationOf(False); |
||||||
|
expect(toBoolean(False)).toEvaluateTo(false); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('fromBoolean', () => { |
||||||
|
it('converts true', () => { |
||||||
|
expect(toBoolean(fromBoolean(true))).toEvaluateTo(true); |
||||||
|
}); |
||||||
|
|
||||||
|
it('converts false', () => { |
||||||
|
expect(toBoolean(fromBoolean(false))).toEvaluateTo(false); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('iif', () => { |
||||||
|
it('returns true argument', () => { |
||||||
|
expect(iif).toDeferEvaluationOf(False, expect.skipped(true), false); |
||||||
|
expect(iif(True)(_(true))(_(false))).toEvaluateTo(true); |
||||||
|
}); |
||||||
|
|
||||||
|
it('returns false argument', () => { |
||||||
|
expect(iif).toDeferEvaluationOf(True, true, expect.skipped(false)); |
||||||
|
expect(iif(False)(_(true))(_(false))).toEvaluateTo(false); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('not', () => { |
||||||
|
it('not False is True', () => { |
||||||
|
expect(not).toDeferEvaluationOf(False); |
||||||
|
expect(toBoolean(not(False))).toEvaluateTo(true); |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
it('not True is False', () => { |
||||||
|
expect(not).toDeferEvaluationOf(True); |
||||||
|
expect(toBoolean(not(True))).toEvaluateTo(false); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('and', () => { |
||||||
|
it('False and False is False', () => { |
||||||
|
expect(and).toDeferEvaluationOf(False, expect.skipped(False)); |
||||||
|
expect(toBoolean(and(False)(False))).toEvaluateTo(false); |
||||||
|
}); |
||||||
|
|
||||||
|
it('False and True is False', () => { |
||||||
|
expect(and).toDeferEvaluationOf(False, expect.skipped(True)); |
||||||
|
expect(toBoolean(and(False)(True))).toEvaluateTo(false); |
||||||
|
}); |
||||||
|
|
||||||
|
it('True and False is False', () => { |
||||||
|
expect(and).toDeferEvaluationOf(True, False); |
||||||
|
expect(toBoolean(and(True)(False))).toEvaluateTo(false); |
||||||
|
}); |
||||||
|
|
||||||
|
it('True and True is True', () => { |
||||||
|
expect(and).toDeferEvaluationOf(True, True); |
||||||
|
expect(toBoolean(and(True)(True))).toEvaluateTo(true); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('or', () => { |
||||||
|
it('False or False is False', () => { |
||||||
|
expect(or).toDeferEvaluationOf(False, False); |
||||||
|
expect(toBoolean(or(False)(False))).toEvaluateTo(false); |
||||||
|
}); |
||||||
|
|
||||||
|
it('False or True is True', () => { |
||||||
|
expect(or).toDeferEvaluationOf(False, True); |
||||||
|
expect(toBoolean(or(False)(True))).toEvaluateTo(true); |
||||||
|
}); |
||||||
|
|
||||||
|
it('True or False is True', () => { |
||||||
|
expect(or).toDeferEvaluationOf(True, expect.skipped(False)); |
||||||
|
expect(toBoolean(or(True)(False))).toEvaluateTo(true); |
||||||
|
}); |
||||||
|
|
||||||
|
it('True or True is True', () => { |
||||||
|
expect(or).toDeferEvaluationOf(True, expect.skipped(True)); |
||||||
|
expect(toBoolean(or(True)(True))).toEvaluateTo(true); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('xor', () => { |
||||||
|
it('False xor False is False', () => { |
||||||
|
expect(xor).toDeferEvaluationOf(False, False); |
||||||
|
expect(toBoolean(xor(False)(False))).toEvaluateTo(false); |
||||||
|
}); |
||||||
|
|
||||||
|
it('False xor True is True', () => { |
||||||
|
expect(xor).toDeferEvaluationOf(False, True); |
||||||
|
expect(toBoolean(xor(False)(True))).toEvaluateTo(true); |
||||||
|
}); |
||||||
|
|
||||||
|
it('True xor False is True', () => { |
||||||
|
expect(xor).toDeferEvaluationOf(True, False); |
||||||
|
expect(toBoolean(xor(True)(False))).toEvaluateTo(true); |
||||||
|
}); |
||||||
|
|
||||||
|
it('True xor True is False', () => { |
||||||
|
expect(xor).toDeferEvaluationOf(True, True); |
||||||
|
expect(toBoolean(xor(True)(True))).toEvaluateTo(false); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
@ -1,10 +1,16 @@ |
|||||||
import { describe, expect, test } from '@jest/globals'; |
import { describe, expect, it } from '@jest/globals'; |
||||||
import { id } from '../src/core'; |
import { _, id, cnst } from '../src/core'; |
||||||
|
|
||||||
describe('id', () => { |
describe('id', () => { |
||||||
test('returns same value', () => { |
it('returns first argument', () => { |
||||||
expect(id(null)).toBe(null); |
expect(id).toDeferEvaluationOf(null); |
||||||
expect(id(1)).toBe(1); |
expect(id(_(null))).toEvaluateTo(null); |
||||||
expect(id(global)).toBe(global); |
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('cnst', () => { |
||||||
|
it('returns first argument after receiving second', () => { |
||||||
|
expect(cnst).toDeferEvaluationOf(null, expect.skipped(null)); |
||||||
|
expect(cnst(_(null))(_('whatever'))).toEvaluateTo(null); |
||||||
}); |
}); |
||||||
}); |
}); |
||||||
|
|||||||
@ -0,0 +1,211 @@ |
|||||||
|
import { describe, expect, it } from '@jest/globals'; |
||||||
|
import { $, _, Lazy } from '../src/core'; |
||||||
|
import { fromBoolean, iif } from '../src/bool'; |
||||||
|
import { toNumber, Zero, sum, succ, Num, Succ, odd } from '../src/num'; |
||||||
|
import { Cons, filter, foldl, foldlz, foldr, foldrz, fromArray, fromNumberArray, head, List, map, Nil, tail, toArray, toNumberArray } from '../src/list'; |
||||||
|
|
||||||
|
describe('List', () => { |
||||||
|
const iterate: (n: number) => Lazy<List<number>> = n => new Lazy(() => Cons(_(n))(iterate(n + 1)).value); |
||||||
|
const niterate: (n: Lazy<Num>) => Lazy<List<Num>> = n => new Lazy(() => Cons(n)(niterate(Succ(n))).value); |
||||||
|
|
||||||
|
describe('toArray', () => { |
||||||
|
it('converts []', () => { |
||||||
|
expect(toArray).toDeferEvaluationOf(Nil); |
||||||
|
expect(toArray(Nil)).toEvaluateTo([]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('converts [0]', () => { |
||||||
|
expect(toArray).toDeferEvaluationOf(Cons(_(0))(Nil)); |
||||||
|
expect(toArray(Cons(_(0))(Nil))).toEvaluateTo([0]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('converts [0, 1]', () => { |
||||||
|
expect(toArray).toDeferEvaluationOf(Cons(_(0))(Cons(_(1))(Nil))); |
||||||
|
expect(toArray(Cons(_(0))(Cons(_(1))(Nil)))).toEvaluateTo([0, 1]); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('fromArray', () => { |
||||||
|
it('converts []', () => { |
||||||
|
expect(toArray(fromArray([]))).toEvaluateTo([]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('converts [0]', () => { |
||||||
|
expect(toArray(fromArray([0]))).toEvaluateTo([0]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('converts 2', () => { |
||||||
|
expect(toArray(fromArray([0, 1]))).toEvaluateTo([0, 1]); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('head', () => { |
||||||
|
it('returns nothing from empty list', () => { |
||||||
|
expect(() => head(Nil)).not.toThrow(); |
||||||
|
expect(() => $(head(Nil))).toThrow(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('returns only item', () => { |
||||||
|
expect(head).toDeferEvaluationOf(Cons(_(0))(Nil)); |
||||||
|
expect(head(Cons(_(0))(Nil))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('returns first item', () => { |
||||||
|
expect(head).toDeferEvaluationOf(Cons(_(0))(Cons(_(1))(Nil))); |
||||||
|
expect(head(Cons(_(0))(Cons(_(1))(Nil)))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('handles infinite lists', () => { |
||||||
|
expect(head(iterate(0))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('tail', () => { |
||||||
|
it('drops nothing from empty list', () => { |
||||||
|
expect(() => tail(Nil)).not.toThrow(); |
||||||
|
expect(() => $(toArray(tail(Nil)))).toThrow(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('drops only item', () => { |
||||||
|
expect(tail).toDeferEvaluationOf(Cons(_(0))(Nil)); |
||||||
|
expect(toArray(tail(Cons(_(0))(Nil)))).toEvaluateTo([]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('drops first item', () => { |
||||||
|
expect(tail).toDeferEvaluationOf(Cons(_(0))(Cons(_(1))(Nil))); |
||||||
|
expect(toArray(tail(Cons(_(0))(Cons(_(1))(Nil))))).toEvaluateTo([1]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('handles infinite lists', () => { |
||||||
|
expect(head(tail(iterate(0)))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('foldl', () => { |
||||||
|
it('folds empty list', () => { |
||||||
|
expect(foldl(sum)).toDeferEvaluationOf(Zero, fromNumberArray([])); |
||||||
|
expect(toNumber(foldl(sum)(Zero)(fromNumberArray([])))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('folds single item', () => { |
||||||
|
expect(foldl(sum)).toDeferEvaluationOf(Zero, fromNumberArray([1])); |
||||||
|
expect(toNumber(foldl(sum)(Zero)(fromNumberArray([1])))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
|
||||||
|
it('folds several items', () => { |
||||||
|
expect(foldl(sum)).toDeferEvaluationOf(Zero, fromNumberArray([1, 2])); |
||||||
|
expect(toNumber(foldl(sum)(Zero)(fromNumberArray([1, 2])))).toEvaluateTo(3); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('foldlz', () => { |
||||||
|
it('does not fold empty list', () => { |
||||||
|
expect(() => foldlz(sum)(Nil)).not.toThrow(); |
||||||
|
expect(() => $(toNumber(foldlz(sum)(Nil)))).toThrow(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('folds single item', () => { |
||||||
|
expect(foldlz(sum)).toDeferEvaluationOf(fromNumberArray([1])); |
||||||
|
expect(toNumber(foldlz(sum)(fromNumberArray([1])))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
|
||||||
|
it('folds several items', () => { |
||||||
|
expect(foldlz(sum)).toDeferEvaluationOf(fromNumberArray([1, 2])); |
||||||
|
expect(toNumber(foldlz(sum)(fromNumberArray([1, 2])))).toEvaluateTo(3); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('foldr', () => { |
||||||
|
it('folds empty list', () => { |
||||||
|
expect(foldr(sum)).toDeferEvaluationOf(Zero, fromNumberArray([])); |
||||||
|
expect(toNumber(foldr(sum)(Zero)(fromNumberArray([])))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('folds single item', () => { |
||||||
|
expect(foldr(sum)).toDeferEvaluationOf(Zero, fromNumberArray([1])); |
||||||
|
expect(toNumber(foldr(sum)(Zero)(fromNumberArray([1])))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
|
||||||
|
it('folds several items', () => { |
||||||
|
expect(foldr(sum)).toDeferEvaluationOf(Zero, fromNumberArray([1, 2])); |
||||||
|
expect(toNumber(foldr(sum)(Zero)(fromNumberArray([1, 2])))).toEvaluateTo(3); |
||||||
|
}); |
||||||
|
|
||||||
|
it('handles infinite lists', () => { |
||||||
|
expect(foldr<number, number>(x => a => |
||||||
|
iif<number> |
||||||
|
(x.then(x => fromBoolean(x === 0))) |
||||||
|
(x) |
||||||
|
(x.then(x => a.then(a => _(x + a)))) |
||||||
|
)(_(0))(iterate(-5))).toEvaluateTo(-15); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('foldrz', () => { |
||||||
|
it('does not fold empty list', () => { |
||||||
|
expect(() => foldrz(sum)(Nil)).not.toThrow(); |
||||||
|
expect(() => $(toNumber(foldrz(sum)(Nil)))).toThrow(); |
||||||
|
}); |
||||||
|
|
||||||
|
it.failing('folds single item', () => { |
||||||
|
expect(foldrz(sum)).toDeferEvaluationOf(fromNumberArray([1])); |
||||||
|
expect(toNumber(foldrz(sum)(fromNumberArray([1])))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
|
||||||
|
it.failing('folds several items', () => { |
||||||
|
expect(foldrz(sum)).toDeferEvaluationOf(fromNumberArray([1, 2])); |
||||||
|
expect(toNumber(foldrz(sum)(fromNumberArray([1, 2])))).toEvaluateTo(3); |
||||||
|
}); |
||||||
|
|
||||||
|
it.failing('handles infinite lists', () => { |
||||||
|
expect(foldrz<number>(x => a => |
||||||
|
iif<number> |
||||||
|
(x.then(x => fromBoolean(x === 0))) |
||||||
|
(x) |
||||||
|
(x.then(x => a.then(a => _(x + a)))) |
||||||
|
)(iterate(-5))).toEvaluateTo(-10); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('map', () => { |
||||||
|
it('maps empty list', () => { |
||||||
|
expect(map(succ)).toDeferEvaluationOf(fromNumberArray([])); |
||||||
|
expect(toNumberArray(map(succ)(fromNumberArray([])))).toEvaluateTo([]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('maps only item', () => { |
||||||
|
expect(map(succ)).toDeferEvaluationOf(fromNumberArray([0])); |
||||||
|
expect(toNumberArray(map(succ)(fromNumberArray([0])))).toEvaluateTo([1]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('maps several items', () => { |
||||||
|
expect(map(succ)).toDeferEvaluationOf(fromNumberArray([0, 1])); |
||||||
|
expect(toNumberArray(map(succ)(fromNumberArray([0, 1])))).toEvaluateTo([1, 2]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('handles infinite lists', () => { |
||||||
|
expect(toNumber(head(map(succ)(niterate(Zero))))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('filter', () => { |
||||||
|
it('filter empty list', () => { |
||||||
|
expect(filter(odd)).toDeferEvaluationOf(fromNumberArray([])); |
||||||
|
expect(toNumberArray(filter(odd)(fromNumberArray([])))).toEvaluateTo([]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('filters only item', () => { |
||||||
|
expect(filter(odd)).toDeferEvaluationOf(fromNumberArray([0])); |
||||||
|
expect(toNumberArray(filter(odd)(fromNumberArray([])))).toEvaluateTo([]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('filters several items', () => { |
||||||
|
expect(filter(odd)).toDeferEvaluationOf(fromNumberArray([0, 1])); |
||||||
|
expect(toNumberArray(filter(odd)(fromNumberArray([0, 1])))).toEvaluateTo([1]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('handles infinite lists', () => { |
||||||
|
expect(toNumber(head(filter(odd)(niterate(Zero))))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
@ -0,0 +1,169 @@ |
|||||||
|
import { describe, expect, it } from '@jest/globals'; |
||||||
|
import { _, Lazy } from '../src/core'; |
||||||
|
import { Num, Zero, Succ, sum, succ, pred, ifz, fromNumber, toNumber, sub, mul } from '../src/num'; |
||||||
|
|
||||||
|
describe('Num', () => { |
||||||
|
describe('toNumber', () => { |
||||||
|
it('converts 0', () => { |
||||||
|
expect(toNumber).toDeferEvaluationOf(Zero); |
||||||
|
expect(toNumber(Zero)).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('converts 1', () => { |
||||||
|
expect(toNumber).toDeferEvaluationOf(Succ(Zero)); |
||||||
|
expect(toNumber(Succ(Zero))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
|
||||||
|
it('converts 2', () => { |
||||||
|
expect(toNumber).toDeferEvaluationOf(Succ(Succ(Zero))); |
||||||
|
expect(toNumber(Succ(Succ(Zero)))).toEvaluateTo(2); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('fromNumber', () => { |
||||||
|
it('converts 0', () => { |
||||||
|
expect(toNumber(fromNumber(0))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('converts 1', () => { |
||||||
|
expect(toNumber(fromNumber(1))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
|
||||||
|
it('converts 2', () => { |
||||||
|
expect(toNumber(fromNumber(2))).toEvaluateTo(2); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('ifz', () => { |
||||||
|
it('returns zero branch', () => { |
||||||
|
expect(ifz).toDeferEvaluationOf(fromNumber(0), true, false); |
||||||
|
expect(ifz(fromNumber(0))(_(true))(_(false))).toEvaluateTo(true); |
||||||
|
}); |
||||||
|
|
||||||
|
it('returns non-zero branch', () => { |
||||||
|
expect(ifz).toDeferEvaluationOf(fromNumber(1), true, false); |
||||||
|
expect(ifz(fromNumber(1))(_(true))(_(false))).toEvaluateTo(false); |
||||||
|
}); |
||||||
|
|
||||||
|
it('returns non-zero branch', () => { |
||||||
|
expect(ifz).toDeferEvaluationOf(fromNumber(100), true, false); |
||||||
|
expect(ifz(fromNumber(10))(_(true))(_(false))).toEvaluateTo(false); |
||||||
|
}); |
||||||
|
|
||||||
|
it('deeply lazy', () => { |
||||||
|
const alot: Lazy<Num> = fromNumber(1000000); |
||||||
|
expect(ifz(Succ(alot))(_(true))(_(false))).toEvaluateTo(false); |
||||||
|
expect(alot.evaluated).toBe(false); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('succ', () => { |
||||||
|
it('succ 0 is 1', () => { |
||||||
|
expect(succ).toDeferEvaluationOf(fromNumber(0)); |
||||||
|
expect(toNumber(succ(fromNumber(0)))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
|
||||||
|
it('succ 1 is 2', () => { |
||||||
|
expect(succ).toDeferEvaluationOf(fromNumber(0)); |
||||||
|
expect(toNumber(succ(fromNumber(1)))).toEvaluateTo(2); |
||||||
|
}); |
||||||
|
|
||||||
|
it('succ 10 is 11', () => { |
||||||
|
expect(succ).toDeferEvaluationOf(fromNumber(10)); |
||||||
|
expect(toNumber(succ(fromNumber(10)))).toEvaluateTo(11); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('pred', () => { |
||||||
|
it('pred 0 is 0', () => { |
||||||
|
expect(pred).toDeferEvaluationOf(fromNumber(0)); |
||||||
|
expect(toNumber(pred(fromNumber(0)))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('pred 1 is 0', () => { |
||||||
|
expect(pred).toDeferEvaluationOf(fromNumber(0)); |
||||||
|
expect(toNumber(pred(fromNumber(1)))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('pred 10 is 9', () => { |
||||||
|
expect(pred).toDeferEvaluationOf(fromNumber(10)); |
||||||
|
expect(toNumber(pred(fromNumber(10)))).toEvaluateTo(9); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('sum', () => { |
||||||
|
it('0 sum 0 is 0', () => { |
||||||
|
expect(sum).toDeferEvaluationOf(fromNumber(0), fromNumber(0)); |
||||||
|
expect(toNumber(sum(fromNumber(0))(fromNumber(0)))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('0 sum 1 is 1', () => { |
||||||
|
expect(sum).toDeferEvaluationOf(fromNumber(0), fromNumber(1)); |
||||||
|
expect(toNumber(sum(fromNumber(0))(fromNumber(1)))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
|
||||||
|
it('1 sum 0 is 1', () => { |
||||||
|
expect(sum).toDeferEvaluationOf(fromNumber(1), fromNumber(0)); |
||||||
|
expect(toNumber(sum(fromNumber(1))(fromNumber(0)))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
|
||||||
|
it('3 sum 4 is 7', () => { |
||||||
|
expect(sum).toDeferEvaluationOf(fromNumber(3), fromNumber(4)); |
||||||
|
expect(toNumber(sum(fromNumber(3))(fromNumber(4)))).toEvaluateTo(7); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('sub', () => { |
||||||
|
it('0 sub 0 is 0', () => { |
||||||
|
expect(sub).toDeferEvaluationOf(fromNumber(0), fromNumber(0)); |
||||||
|
expect(toNumber(sub(fromNumber(0))(fromNumber(0)))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('0 sub 1 is 0', () => { |
||||||
|
expect(sub).toDeferEvaluationOf(fromNumber(0), fromNumber(1)); |
||||||
|
expect(toNumber(sub(fromNumber(0))(fromNumber(1)))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('1 sub 0 is 1', () => { |
||||||
|
expect(sub).toDeferEvaluationOf(fromNumber(1), fromNumber(0)); |
||||||
|
expect(toNumber(sub(fromNumber(1))(fromNumber(0)))).toEvaluateTo(1); |
||||||
|
}); |
||||||
|
|
||||||
|
it('7 sub 4 is 3', () => { |
||||||
|
expect(sub).toDeferEvaluationOf(fromNumber(7), fromNumber(4)); |
||||||
|
expect(toNumber(sub(fromNumber(7))(fromNumber(4)))).toEvaluateTo(3); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('mul', () => { |
||||||
|
it('0 mul 0 is 0', () => { |
||||||
|
expect(mul).toDeferEvaluationOf(fromNumber(0), fromNumber(0)); |
||||||
|
expect(toNumber(mul(fromNumber(0))(fromNumber(0)))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('0 mul 10 is 0', () => { |
||||||
|
expect(mul).toDeferEvaluationOf(fromNumber(0), expect.skipped(fromNumber(10))); |
||||||
|
expect(toNumber(mul(fromNumber(0))(fromNumber(10)))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('10 mul 0 is 0', () => { |
||||||
|
expect(mul).toDeferEvaluationOf(fromNumber(10), fromNumber(0)); |
||||||
|
expect(toNumber(mul(fromNumber(1))(fromNumber(0)))).toEvaluateTo(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('1 mul 10 is 10', () => { |
||||||
|
expect(mul).toDeferEvaluationOf(fromNumber(1), fromNumber(10)); |
||||||
|
expect(toNumber(mul(fromNumber(1))(fromNumber(10)))).toEvaluateTo(10); |
||||||
|
}); |
||||||
|
|
||||||
|
it('10 mul 1 is 10', () => { |
||||||
|
expect(mul).toDeferEvaluationOf(fromNumber(10), fromNumber(1)); |
||||||
|
expect(toNumber(mul(fromNumber(10))(fromNumber(1)))).toEvaluateTo(10); |
||||||
|
}); |
||||||
|
|
||||||
|
it('3 mul 4 is 12', () => { |
||||||
|
expect(mul).toDeferEvaluationOf(fromNumber(3), fromNumber(4)); |
||||||
|
expect(toNumber(mul(fromNumber(3))(fromNumber(4)))).toEvaluateTo(12); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
{ |
||||||
|
"extends": "../tsconfig.json", |
||||||
|
"include": [ |
||||||
|
"**/*.ts", |
||||||
|
], |
||||||
|
} |
||||||
@ -0,0 +1,96 @@ |
|||||||
|
import { expect } from '@jest/globals'; |
||||||
|
import type { MatcherFunction } from 'expect'; |
||||||
|
import { $, _, Lazy } from '../src/core'; |
||||||
|
|
||||||
|
class Deferrable<T> { |
||||||
|
constructor( |
||||||
|
public readonly when: 'always' | 'deferred' | 'never', |
||||||
|
public readonly what: Lazy<T>) { } |
||||||
|
} |
||||||
|
|
||||||
|
const toEvaluateTo: MatcherFunction<[expected: unknown]> = |
||||||
|
function (actual, expected) { |
||||||
|
if (!(actual instanceof Lazy)) { |
||||||
|
throw new TypeError('Actual must be of type Lazy!'); |
||||||
|
} |
||||||
|
if (this.equals($(actual), expected)) { |
||||||
|
return { |
||||||
|
message: () => `expected ${this.utils.printReceived($(actual))} not to evaluate to ${this.utils.printExpected(expected)}`, |
||||||
|
pass: true, |
||||||
|
}; |
||||||
|
} else { |
||||||
|
return { |
||||||
|
message: () => `expected ${this.utils.printReceived($(actual))} to evaluate to ${this.utils.printExpected(expected)}`, |
||||||
|
pass: false, |
||||||
|
}; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const toDeferEvaluationOf: MatcherFunction<(unknown | Deferrable<unknown>)[]> = |
||||||
|
function (actual, ...args) { |
||||||
|
if (typeof actual !== 'function') { |
||||||
|
throw new TypeError('Actual must be of type function!'); |
||||||
|
} |
||||||
|
if (this.isNot) { |
||||||
|
throw new TypeError('`toDeferEvaluationOf` can not be `not`ted, use `always`/`deferred`/`never` on arguments!'); |
||||||
|
} |
||||||
|
const largs: Deferrable<unknown>[] = args.map(v => |
||||||
|
v instanceof Deferrable ? v |
||||||
|
: v instanceof Lazy ? new Deferrable('deferred', _(v.value)) |
||||||
|
: new Deferrable('deferred', _(v))); |
||||||
|
const cargs: Deferrable<unknown>[] = []; |
||||||
|
let result = actual; |
||||||
|
for (const la of largs) { |
||||||
|
result = result(la.what); |
||||||
|
cargs.push(la); |
||||||
|
const mismatchingArg: Deferrable<unknown> | undefined = cargs.find(v => v.when === 'always' ? !v.what.evaluated : v.what.evaluated); |
||||||
|
if (mismatchingArg) { |
||||||
|
return { |
||||||
|
message: () => |
||||||
|
mismatchingArg.when === 'always' |
||||||
|
? `expected ${this.utils.printExpected(actual.name)} to evaluate ${this.utils.printReceived(`${largs.indexOf(mismatchingArg) + 1}th argument`)} immediately` |
||||||
|
: `expected ${this.utils.printExpected(actual.name)} not to evaluate ${this.utils.printReceived(`${largs.indexOf(mismatchingArg) + 1}th argument`)} immediately`, |
||||||
|
pass: false, |
||||||
|
}; |
||||||
|
} |
||||||
|
} |
||||||
|
if (!(result instanceof Lazy)) { |
||||||
|
throw new TypeError('Actual did not resolve to value, more arguments expected.'); |
||||||
|
} |
||||||
|
result.value; |
||||||
|
const mismatchingArg: Deferrable<unknown> | undefined = cargs.find(v => v.when === 'never' && v.what.evaluated); |
||||||
|
if (mismatchingArg) { |
||||||
|
return { |
||||||
|
message: () => `expected ${this.utils.printExpected(actual.name)} not to evaluate ${this.utils.printReceived(`${largs.indexOf(mismatchingArg) + 1}th argument`)}`, |
||||||
|
pass: false, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
message: () => `expected ${this.utils.printExpected('toDeferEvaluationOf')} to ${this.utils.printReceived('fail')}`, |
||||||
|
pass: true, |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
expect.extend({ |
||||||
|
toEvaluateTo, |
||||||
|
toDeferEvaluationOf, |
||||||
|
}); |
||||||
|
|
||||||
|
expect.evaluated = (v) => new Deferrable('always', _(v)); |
||||||
|
expect.deferred = (v) => new Deferrable('deferred', _(v)); |
||||||
|
expect.skipped = (v) => new Deferrable('never', _(v)); |
||||||
|
|
||||||
|
declare module 'expect' { |
||||||
|
interface AsymmetricMatchers { |
||||||
|
evaluated<T>(value: T): Deferrable<T>; |
||||||
|
deferred<T>(value: T): Deferrable<T>; |
||||||
|
skipped<T>(value: T): Deferrable<T>; |
||||||
|
toEvaluateTo(value: any): void; |
||||||
|
toDeferEvaluationOf(...args: any[]): void; |
||||||
|
} |
||||||
|
interface Matchers<R> { |
||||||
|
toEvaluateTo(value: any): R; |
||||||
|
toDeferEvaluationOf(...args: any[]): R; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue