diff --git a/src/bool.ts b/src/bool.ts index b29e1e8..9f4ff24 100644 --- a/src/bool.ts +++ b/src/bool.ts @@ -11,6 +11,9 @@ export const False: Bool export const iif: L<(b: Bool) => L<(t: T) => L<(f: T) => T>>> = _(b => _(t => _(f => b._(t)._(f)))); +export const eq: L<(l: Bool) => L<(r: Bool) => Bool>> + = _(l => _(r => l._(r)._(not._(r)))) + export const not: L<(b: Bool) => Bool> = _(b => b._(False)._(True)); diff --git a/src/list.ts b/src/list.ts index 5caaec1..6484a8a 100644 --- a/src/list.ts +++ b/src/list.ts @@ -1,6 +1,6 @@ import { L, P, _, fromNative, toNative, undef } from './core'; import { Num, fromNumber, toNumber } from './num'; -import { Bool, iif } from './bool'; +import { Bool, False, True, and, iif, or } from './bool'; export type List = L<(z: R) => L<(f: L<(x: T) => L<(xs: List) => R>>) => R>>; @@ -42,6 +42,14 @@ export const foldr: L<(f: L<(x: T) => L<(a: R) => R>>) export const foldrz: L<(f: L<(x: T) => L<(a: T) => T>>) => L<(xs: List) => T>> = _(_f => _(_z => undef)); +export const eq: L<(e: L<(l: T) =>L <(r: T) => Bool>>) => L<(l: List)=> L<(r: List) => Bool>>> + = + _((e: L<(l: T) => L<(r: T) => Bool>>) => + _((l: List) => + _((r: List) => l + ._(r._(True)._(_(_x => _(_xs => False)))) + ._(_(lx => _(lxs => r._(False)._(_(rx => _(rxs => and._(e._(lx)._(rx))._(eq._(e)._(lxs)._(rxs))))))))))); + export const map: L<(f: L<(x: T) => R>) => L<(xs: List) => List>> = _((f: L<(x: T) => R>) => @@ -52,6 +60,16 @@ export const filter: L<(f: L<(x: T) => Bool>) => L<(xs: List) => _((f: L<(x: T) => Bool>) => _((xs: List) => xs._(Nil)._(_(x => _(xs => iif._(f._(x))._(Cons._(x)._(filter._(f)._(xs)))._(filter._(f)._(xs))))))); +export const any: L<(f: L<(x: T) => Bool>) => L<(xs: List) => Bool>> + = + _((f: L<(x: T) => Bool>) => + _((xs: List) => xs._(False)._(_(x => _(xs => or._(f._(x))._(any._(f)._(xs))))))); + +export const all: L<(f: L<(x: T) => Bool>) => L<(xs: List) => Bool>> + = + _((f: L<(x: T) => Bool>) => + _((xs: List) => xs._(True)._(_(x => _(xs => and._(f._(x))._(all._(f)._(xs))))))); + export const fromArray: (xs: T[]) => List = xs => xs.length === 0 ? Nil : Cons._(xs[0])._(new L(() => fromArray(xs.slice(1)).value)); diff --git a/src/num.ts b/src/num.ts index 077263e..233d28a 100644 --- a/src/num.ts +++ b/src/num.ts @@ -18,6 +18,11 @@ export const succ: L<(n: Num) => Num> export const pred: L<(n: Num) => Num> = _(n => n._(Zero)._(id)); +export const eq: L<(l: Num) => L<(r: Num) => Bool>> + = _(l => _(r => l + ._(r._(True)._(_(_ => False))) + ._(_(pl => r._(False)._(_(pr => eq._(pl)._(pr))))))); + export const even: L<(n: Num) => Bool> = _(n => n._(True)._(odd)); diff --git a/test/bool.spec.ts b/test/bool.spec.ts index b1cf979..292a592 100644 --- a/test/bool.spec.ts +++ b/test/bool.spec.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from '@jest/globals'; -import { toBoolean as $, False, True, fromBoolean as _, and, iif, not, or, xor } from '../src/bool'; +import { toBoolean as $, False, True, fromBoolean as _, and, eq, iif, not, or, xor } from '../src/bool'; describe('Bool', () => { describe('toBoolean', () => { @@ -34,6 +34,28 @@ describe('Bool', () => { }); }); + describe('eq', () => { + it('False eq False is True', () => { + expect(eq).toDeferEvaluationOf(False, False); + expect($(eq._(False)._(False))).toBe(true); + }); + + it('False eq True is False', () => { + expect(eq).toDeferEvaluationOf(False, True); + expect($(eq._(False)._(True))).toBe(false); + }); + + it('True eq False is False', () => { + expect(eq).toDeferEvaluationOf(True, False); + expect($(eq._(True)._(False))).toBe(false); + }); + + it('True eq True is True', () => { + expect(eq).toDeferEvaluationOf(True, True); + expect($(eq._(True)._(True))).toBe(true); + }); + }); + describe('not', () => { it('not False is True', () => { expect(not).toDeferEvaluationOf(False); diff --git a/test/list.spec.ts b/test/list.spec.ts index e6518c4..b0ccd45 100644 --- a/test/list.spec.ts +++ b/test/list.spec.ts @@ -1,41 +1,44 @@ import { describe, expect, it } from '@jest/globals'; -import { L, toNative } from '../src/core'; -import { toNumber as $n, Num, Succ, Zero, fromNumber as _n, odd, succ, sum } from '../src/num'; -import { toArray as $, toNumberArray as $na, Cons, List, Nil, fromArray as _, fromNumberArray as _na, filter, foldl, foldlz, foldr, foldrz, head, map, tail } from '../src/list'; +import { L, _, toNative } from '../src/core'; +import { toBoolean as $b, iif } from '../src/bool'; +import { toNumber as $n, Num, Succ, Zero, fromNumber as _n, eq as eqn, even, odd, succ, sum } from '../src/num'; +import { toArray as $a, toNumberArray as $na, Cons, List, Nil, fromArray as _a, fromNumberArray as _na, all, any, eq, filter, foldl, foldlz, foldr, foldrz, head, map, tail } from '../src/list'; + describe('List', () => { const iterate: (n: Num) => List = n => new L(() => Cons._(n)._(iterate(Succ._(n))).value); + const infinite: List = iterate(Zero); describe('toArray', () => { it('converts []', () => { - expect($(Nil)).toEqual([]); + expect($a(Nil)).toEqual([]); }); it('converts [0]', () => { - expect($(Cons._(_n(0))._(Nil)).map($n)).toEqual([0]); + expect($a(Cons._(_n(0))._(Nil)).map($n)).toEqual([0]); }); it('converts [0, 1]', () => { - expect($(Cons._(_n(0))._(Cons._(_n(1))._(Nil))).map($n)).toEqual([0, 1]); + expect($a(Cons._(_n(0))._(Cons._(_n(1))._(Nil))).map($n)).toEqual([0, 1]); }); }); describe('fromArray', () => { it('converts []', () => { - expect($(_([]))).toEqual([]); + expect($a(_a([]))).toEqual([]); }); it('converts [0]', () => { - expect($(_([0].map(_n))).map($n)).toEqual([0]); + expect($a(_a([0].map(_n))).map($n)).toEqual([0]); }); it('converts 2', () => { - expect($(_([0, 1].map(_n))).map($n)).toEqual([0, 1]); + expect($a(_a([0, 1].map(_n))).map($n)).toEqual([0, 1]); }); }); describe('head', () => { - it('returns nothing from empty list', () => { + it('returns nothing from Nil', () => { expect(() => head._(Nil)).not.toThrow(); expect(() => toNative(head._(Nil))).toThrow(); }); @@ -51,12 +54,12 @@ describe('List', () => { }); it('handles infinite lists', () => { - expect($n(head._(iterate(Zero)))).toBe(0); + expect($n(head._(infinite))).toBe(0); }); }); describe('tail', () => { - it('drops nothing from empty list', () => { + it('drops nothing from Nil', () => { expect(() => tail._(Nil)).not.toThrow(); expect(() => $na(tail._(Nil))).toThrow(); }); @@ -72,12 +75,12 @@ describe('List', () => { }); it('handles infinite lists', () => { - expect($n(head._(tail._(iterate(Zero))))).toBe(1); + expect($n(head._(tail._(infinite)))).toBe(1); }); }); describe('foldl', () => { - it('folds empty list', () => { + it('folds Nil', () => { expect(foldl._(sum)).toDeferEvaluationOf(Zero, _na([])); expect($n(foldl._(sum)._(Zero)._(_na([])))).toBe(0); }); @@ -94,7 +97,7 @@ describe('List', () => { }); describe('foldlz', () => { - it('does not fold empty list', () => { + it('does not fold Nil', () => { expect(() => foldlz._(sum)._(Nil)).not.toThrow(); expect(() => $n(foldlz._(sum)._(Nil))).toThrow(); }); @@ -111,7 +114,7 @@ describe('List', () => { }); describe('foldr', () => { - it('folds empty list', () => { + it('folds Nil', () => { expect(foldr._(sum)).toDeferEvaluationOf(Zero, _na([])); expect($n(foldr._(sum)._(Zero)._(_na([])))).toBe(0); }); @@ -126,19 +129,18 @@ describe('List', () => { expect($n(foldr._(sum)._(Zero)._(_na([1, 2])))).toBe(3); }); - // it('handles infinite lists', () => { - // expect(foldr, Lazy>(x => _(a => - // iif> - // (x.call(x => fromBoolean(x === 0))) - // .call( - // (x).call( - // (x.call(x => a.call(a => _(x + a))))))) - // ).call((fromNumber(0)).call((iterate(-5))))).toEvaluateTo(-15); - // }); + it('handles infinite lists', () => { + expect($n( + foldr + ._(_(x => _(a => iif._(eqn._(x)._(_n(5)))._(x)._(sum._(x)._(a))))) + ._(Zero) + ._(infinite))) + .toBe(15); + }); }); describe('foldrz', () => { - it('does not fold empty list', () => { + it('does not fold Nil', () => { expect(() => foldrz._(sum)._(Nil)).not.toThrow(); expect(() => $n(foldrz._(sum)._(Nil))).toThrow(); }); @@ -153,19 +155,65 @@ describe('List', () => { expect($n(foldrz._(sum)._(_na([1, 2])))).toBe(3); }); - // it.failing('handles infinite lists', () => { - // expect(foldrz>(x => _(a => - // iif> - // (x.call(x => fromBoolean(x === 0))) - // .call( - // (x).call( - // (x.call(x => a.call(a => _(x + a))))))) - // ).call((iterate(-5)))).toEvaluateTo(-10); - // }); + it.failing('handles infinite lists', () => { + expect($n( + foldrz + ._(_(x => _(a => iif._(eqn._(x)._(_n(5)))._(x)._(sum._(x)._(a))))) + ._(infinite))) + .toBe(15); + }); + }); + + describe('eq', () => { + it('Nil eq Nil is True', () => { + expect(eq).toDeferEvaluationOf(eqn, _na([]), _na([])); + expect($b(eq._(eqn)._(_na([]))._(_na([])))).toEqual(true); + }); + + it('Nil eq [0, 1, 2] is False', () => { + expect(eq).toDeferEvaluationOf(eqn, _na([]), _na([0, 1, 2])); + expect($b(eq._(eqn)._(_na([]))._(_na([0, 1, 2])))).toEqual(false); + }); + + it('[0, 1, 2] eq Nil is False', () => { + expect(eq).toDeferEvaluationOf(eqn, _na([0, 1, 2]), _na([])); + expect($b(eq._(eqn)._(_na([0, 1, 2]))._(_na([])))).toEqual(false); + }); + + it('[0] eq [0, 1, 2] is False', () => { + expect(eq).toDeferEvaluationOf(eqn, _na([0]), _na([0, 1, 2])); + expect($b(eq._(eqn)._(_na([0]))._(_na([0, 1, 2])))).toEqual(false); + }); + + it('[0, 1, 2] eq [0] is False', () => { + expect(eq).toDeferEvaluationOf(eqn, _na([0, 1, 2]), _na([0])); + expect($b(eq._(eqn)._(_na([0, 1, 2]))._(_na([0])))).toEqual(false); + }); + + + it('[1, 2, 0] eq [0, 1, 2] is False', () => { + expect(eq).toDeferEvaluationOf(eqn, _na([1, 2, 0]), _na([0, 1, 2])); + expect($b(eq._(eqn)._(_na([1, 2, 0]))._(_na([0, 1, 2])))).toEqual(false); + }); + + it('[0, 1, 2] eq [1, 2, 0] is False', () => { + expect(eq).toDeferEvaluationOf(eqn, _na([0, 1, 2]), _na([1, 2, 0])); + expect($b(eq._(eqn)._(_na([0, 1, 2]))._(_na([1, 2, 0])))).toEqual(false); + }); + + it('[0, 1, 2] eq [0, 1, 2] is True', () => { + expect(eq).toDeferEvaluationOf(eqn, _na([0, 1, 2]), _na([0, 1, 2])); + expect($b(eq._(eqn)._(_na([0, 1, 2]))._(_na([0, 1, 2])))).toEqual(true); + }); + + it('handles infinite lists', () => { + expect($b(eq._(eqn)._(infinite)._(_na([0, 1, 2])))).toEqual(false); + expect($b(eq._(eqn)._(_na([0, 1, 2]))._(infinite))).toEqual(false); + }); }); describe('map', () => { - it('maps empty list', () => { + it('maps Nil', () => { expect(map._(succ)).toDeferEvaluationOf(_na([])); expect($na(map._(succ)._(_na([])))).toEqual([]); }); @@ -181,12 +229,12 @@ describe('List', () => { }); it('handles infinite lists', () => { - expect($n(head._(map._(succ)._(iterate(Zero))))).toBe(1); + expect($n(head._(map._(succ)._(infinite)))).toBe(1); }); }); describe('filter', () => { - it('filter empty list', () => { + it('filter Nil', () => { expect(filter._(odd)).toDeferEvaluationOf(_na([])); expect($na(filter._(odd)._(_na([])))).toEqual([]); }); @@ -202,7 +250,49 @@ describe('List', () => { }); it('handles infinite lists', () => { - expect($n(head._(filter._(odd)._(iterate(Zero))))).toBe(1); + expect($n(head._(filter._(odd)._(infinite)))).toBe(1); + }); + }); + + describe('any', () => { + it('any Nil is False', () => { + expect(any._(odd)).toDeferEvaluationOf(_na([])); + expect($b(any._(odd)._(_na([])))).toEqual(false); + }); + + it('any for no matching item is False', () => { + expect(any._(odd)).toDeferEvaluationOf(_na([0, 2])); + expect($b(any._(odd)._(_na([0, 2])))).toEqual(false); + }); + + it('any for matching item is True', () => { + expect(any._(odd)).toDeferEvaluationOf(_na([0, 1, 2])); + expect($b(any._(odd)._(_na([0, 1, 2])))).toEqual(true); + }); + + it('handles infinite lists', () => { + expect($b(any._(odd)._(infinite))).toEqual(true); + }); + }); + + describe('all', () => { + it('all Nil is True', () => { + expect(all._(even)).toDeferEvaluationOf(_na([])); + expect($b(all._(even)._(_na([])))).toEqual(true); + }); + + it('all for non-matching item is False', () => { + expect(all._(even)).toDeferEvaluationOf(_na([0, 1, 2])); + expect($b(all._(even)._(_na([0, 1, 2])))).toEqual(false); + }); + + it('all for no non-matching item is True', () => { + expect(all._(even)).toDeferEvaluationOf(_na([0, 2])); + expect($b(all._(even)._(_na([0, 2])))).toEqual(true); + }); + + it('handles infinite lists', () => { + expect($b(all._(even)._(infinite))).toEqual(false); }); }); }); diff --git a/test/num.spec.ts b/test/num.spec.ts index d8ed243..f6cef47 100644 --- a/test/num.spec.ts +++ b/test/num.spec.ts @@ -1,7 +1,11 @@ import { describe, expect, it } from '@jest/globals'; -import { toNumber as $, Num, Succ, Zero, fromNumber as _, ifz, mul, pred, sub, succ, sum } from '../src/num'; +import { L } from '../src/core'; +import { toBoolean as $b, fromBoolean as _b } from '../src/bool'; +import { toNumber as $, Num, Succ, Zero, fromNumber as _, eq, ifz, mul, pred, sub, succ, sum } from '../src/num'; describe('Num', () => { + const infinity: Num = new L(() => Succ._(infinity).value); + describe('toNumber', () => { it('converts 0', () => { expect($(Zero)).toBe(0); @@ -33,23 +37,21 @@ describe('Num', () => { describe('ifz', () => { it('returns zero branch', () => { expect(ifz).toDeferEvaluationOf(_(0), true, false); - expect($(ifz._(_(0))._(_(0))._(_(1)))).toBe(0); + expect($b(ifz._(_(0))._(_b(true))._(_b(false)))).toBe(true); }); it('returns non-zero branch', () => { expect(ifz).toDeferEvaluationOf(_(1), true, false); - expect($(ifz._(_(1))._(_(0))._(_(1)))).toBe(1); + expect($b(ifz._(_(1))._(_b(true))._(_b(false)))).toBe(false); }); it('returns non-zero branch', () => { - expect(ifz).toDeferEvaluationOf(_(100), true, false); - expect($(ifz._(_(10))._(_(0))._(_(1)))).toBe(1); + expect(ifz).toDeferEvaluationOf(_(10), true, false); + expect($b(ifz._(_(10))._(_b(true))._(_b(false)))).toBe(false); }); - it('deeply lazy', () => { - const alot: Num = _(1000000); - expect($(ifz._(Succ._(alot))._(_(0))._(_(1)))).toBe(1); - expect(alot.evaluated).toBe(false); + it('handles infinity', () => { + expect($b(ifz._(infinity)._(_b(true))._(_b(false)))).toBe(false); }); }); @@ -87,6 +89,43 @@ describe('Num', () => { }); }); + describe('eq', () => { + it('0 eq 0 is True', () => { + expect(eq).toDeferEvaluationOf(_(0), _(0)); + expect($b(eq._(_(0))._(_(0)))).toBe(true); + }); + + it('0 eq 1 is False', () => { + expect(eq).toDeferEvaluationOf(_(0), _(1)); + expect($b(eq._(_(0))._(_(1)))).toBe(false); + }); + + it('1 eq 0 is False', () => { + expect(eq).toDeferEvaluationOf(_(1), _(0)); + expect($b(eq._(_(1))._(_(0)))).toBe(false); + }); + + it('3 eq 3 is True', () => { + expect(eq).toDeferEvaluationOf(_(3), _(4)); + expect($b(eq._(_(3))._(_(3)))).toBe(true); + }); + + it('3 eq 7 is False', () => { + expect(eq).toDeferEvaluationOf(_(3), _(7)); + expect($b(eq._(_(3))._(_(7)))).toBe(false); + }); + + it('7 eq 3 is False', () => { + expect(eq).toDeferEvaluationOf(_(7), _(3)); + expect($b(eq._(_(7))._(_(3)))).toBe(false); + }); + + it('handles infinity', () => { + expect($b(eq._(infinity)._(_(3)))).toBe(false); + expect($b(eq._(_(7))._(infinity))).toBe(false); + }) + }); + describe('sum', () => { it('0 sum 0 is 0', () => { expect(sum).toDeferEvaluationOf(_(0), _(0));