From 66666617e1261bb5e77d70c26c966cadf47b7786 Mon Sep 17 00:00:00 2001 From: Freywar Ulvnaudgari Date: Mon, 5 Aug 2024 22:57:46 +0300 Subject: [PATCH] More library functions --- src/bool.ts | 4 +- src/char.ts | 18 ++++++++ src/index.ts | 2 + src/list.ts | 13 +++--- src/num.ts | 4 +- src/pair.ts | 31 +++++++++++++ src/string.ts | 28 ++++++++++++ test/char.spec.ts | 64 ++++++++++++++++++++++++++ test/list.spec.ts | 47 ++++++++++++++++++-- test/pair.spec.ts | 54 ++++++++++++++++++++++ test/string.spec.ts | 106 ++++++++++++++++++++++++++++++++++++++++++++ 11 files changed, 359 insertions(+), 12 deletions(-) create mode 100644 src/char.ts create mode 100644 src/pair.ts create mode 100644 src/string.ts create mode 100644 test/char.spec.ts create mode 100644 test/pair.spec.ts create mode 100644 test/string.spec.ts diff --git a/src/bool.ts b/src/bool.ts index 9f4ff24..2e2802c 100644 --- a/src/bool.ts +++ b/src/bool.ts @@ -1,4 +1,4 @@ -import { L, P, _, fromNative, toNative } from './core'; +import { toNative as $n, L, P, _, fromNative as _n } from './core'; export type Bool = L<(t: T) => L<(f: T) => T>>; @@ -30,4 +30,4 @@ export const fromBoolean: (b: boolean) => Bool = b => b ? True : False; export const toBoolean: (b: Bool) => boolean - = b => toNative(b._(fromNative(true))._(fromNative(false))); + = b => $n(b._(_n(true))._(_n(false))); diff --git a/src/char.ts b/src/char.ts new file mode 100644 index 0000000..05503e7 --- /dev/null +++ b/src/char.ts @@ -0,0 +1,18 @@ + +import { L, _ } from './core'; +import { Bool } from './bool'; +import { toNumber as $n, Num, Zero, fromNumber as _n, eq as eqn } from './num'; + +export type Char = Num; + +export const Null: Char + = Zero; + +export const eq: L<(l: Char) => L<(r: Char) => Bool>> + = eqn; + +export const fromChar: (c: string) => Char + = c => _n(c.charCodeAt(0)); + +export const toChar: (c: Char) => string + = c => String.fromCharCode($n(c)); diff --git a/src/index.ts b/src/index.ts index f140b65..e4c7cd7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,5 @@ export * from './core'; export * from './bool'; export * from './num'; export * from './list'; +export * from './char'; +export * from './pair'; diff --git a/src/list.ts b/src/list.ts index 6484a8a..db02c3e 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 { toNative as $n, L, P, _, fromNative as _n, undef } from './core'; import { Bool, False, True, and, iif, or } from './bool'; +import { toNumber as $nn, Num, fromNumber as _nn } from './num'; export type List = L<(z: R) => L<(f: L<(x: T) => L<(xs: List) => R>>) => R>>; @@ -13,6 +13,9 @@ export const Cons: L<(x: T) => L<(xs: List) => List>> export const cons: L<(x: T) => L<(xs: List) => List>> = Cons; +export const concat: L<(l: List) => L<(r: List) => List>> + = _(l => _(r => l._(r)._(_(x => _(xs => cons._(x)._(concat._(xs)._(r))))))); + export const head: L<(xs: List) => T> = _(xs => xs._(undef)._(_(x => _(_xs => x)))); @@ -74,10 +77,10 @@ export const fromArray: (xs: T[]) => List = xs => xs.length === 0 ? Nil : Cons._(xs[0])._(new L(() => fromArray(xs.slice(1)).value)); export const fromNumberArray: (xs: number[]) => List - = xs => xs.length === 0 ? Nil : Cons._(fromNumber(xs[0]))._(new L(() => fromNumberArray(xs.slice(1)).value)); + = xs => xs.length === 0 ? Nil : Cons._(_nn(xs[0]))._(new L(() => fromNumberArray(xs.slice(1)).value)); export const toArray: (xs: List) => P[] - = xs => toNative(xs._(fromNative([]))._(_(x => _(xs => fromNative([x, ...toArray(xs)]))))); + = xs => $n(xs._(_n([]))._(_(x => _(xs => _n([x, ...toArray(xs)]))))); export const toNumberArray: (xs: List) => number[] - = xs => toNative(xs._(fromNative([]))._(_(x => _(xs => fromNative([toNumber(x), ...toNumberArray(xs)]))))); + = xs => $n(xs._(_n([]))._(_(x => _(xs => _n([$nn(x), ...toNumberArray(xs)]))))); diff --git a/src/num.ts b/src/num.ts index 233d28a..06ee70c 100644 --- a/src/num.ts +++ b/src/num.ts @@ -1,5 +1,5 @@ +import { toNative as $n, L, P, _, fromNative as _n, id } from './core'; import { Bool, False, True } from './bool'; -import { L, P, _, fromNative, id, toNative } from './core'; export type Num = L<(z: T) => L<(n: L<(p: Num) => T>) => T>>; @@ -42,4 +42,4 @@ export const fromNumber: (n: number) => Num = n => !n ? Zero : Succ._(new L(() => fromNumber(n - 1).value)); export const toNumber: (n: Num) => number - = n => toNative(n._(fromNative(0))._(_(p => fromNative(1 + toNumber(p))))); + = n => $n(n._(_n(0))._(_(p => _n(1 + toNumber(p))))); diff --git a/src/pair.ts b/src/pair.ts new file mode 100644 index 0000000..f1cccff --- /dev/null +++ b/src/pair.ts @@ -0,0 +1,31 @@ +import { L, P, _ } from './core'; + +export type Pair = L<(f: L<(f: F) => L<(s: S) => R>>) => R>; + +export const Pair: L<(f: F) => L<(s: S) => Pair>> + = + _((f: F) => + _((s: S) => + _((p: L<(f: F) => L<(s: S) => R>>) => p._(f)._(s)))); + +export const fst: L<(p: Pair) => F> + = _(p => p._(_(f => _(_s => f)))); + +export const snd: L<(p: Pair) => S> + = _(p => p._(_(_f => _(s => s)))); + +export const first: L<(t: L<(f: F) => FR>) => L<(p: Pair) => Pair>> + = + _((t: L<(f: F) => FR>) => + _((p: Pair) => p._(_(f => _(s => Pair._(t._(f))._(s)))))); + +export const second: L<(t: L<(s: S) => SR>) => L<(p: Pair) => Pair>> + = + _((t: L<(s: S) => SR>) => + _((p: Pair) => p._(_(f => _(s => Pair._(f)._(t._(s))))))); + +export const both: L<(tf: L<(f: F) => FR>) => L<(ts: L<(s: S) => SR>) =>L<(p: Pair) => Pair>>> + = + _((tf: L<(f: F) => FR>) => + _((ts: L<(s: S) => SR>) => + _((p: Pair) => p._(_(f => _(s => Pair._(tf._(f))._(ts._(s)))))))); diff --git a/src/string.ts b/src/string.ts new file mode 100644 index 0000000..3b68304 --- /dev/null +++ b/src/string.ts @@ -0,0 +1,28 @@ +import { toNative as $n, L, _, fromNative as _n } from './core'; +import { Bool } from './bool'; +import { fromNumber as _nn } from './num'; +import { toChar as $c, Char, fromChar as _c, eq as eqc } from './char'; +import { Cons as ConsL, List, Nil, concat as concatl, eq as eql } from './list'; + +export type String = List; + +export const Empty: String + = Nil; + +export const Cons: L<(c: Char) => L<(s: String) => String>> + = ConsL; + +export const cons: L<(c: Char) => L<(s: String) => String>> + = Cons; + +export const concat: L<(l: String) => L<(r: String) => String>> + = concatl; + +export const eq: L<(l: String) => L<(r: String) => Bool>> + = eql._(eqc); + +export const fromString: (xs: string) => String + = xs => xs.length === 0 ? Nil : Cons._(_c(xs[0]))._(new L(() => fromString(xs.slice(1)).value)); + +export const toString: (xs: String) => string + = xs => $n(xs._(_n(''))._(_(x => _(xs => _n($c(x) + toString(xs)))))); diff --git a/test/char.spec.ts b/test/char.spec.ts new file mode 100644 index 0000000..a477093 --- /dev/null +++ b/test/char.spec.ts @@ -0,0 +1,64 @@ +import { describe, expect, it } from '@jest/globals'; +import { toBoolean as $b, fromBoolean as _b } from '../src/bool'; +import { fromNumber as _n } from '../src/num'; +import { toChar as $, fromChar as _, eq } from '../src/char'; + +describe('Char', () => { + describe('toChar', () => { + it('converts "0"', () => { + expect($(_n(48))).toBe('0'); + }); + + it('converts "a"', () => { + expect($(_n(97))).toBe('a'); + }); + + it('converts "Z"', () => { + expect($(_n(90))).toBe('Z'); + }); + + it('converts "-"', () => { + expect($(_n(45))).toBe('-'); + }); + }); + + describe('fromChar', () => { + it('converts "0"', () => { + expect($(_('0'))).toBe('0'); + }); + + it('converts "a"', () => { + expect($(_('a'))).toBe('a'); + }); + + it('converts "Z"', () => { + expect($(_('Z'))).toBe('Z'); + }); + + it('converts "-"', () => { + expect($(_('-'))).toBe('-'); + }); + }); + + describe('eq', () => { + it('"0" eq "0" is True', () => { + expect(eq).toDeferEvaluationOf(_('0'), _('0')); + expect($b(eq._(_('0'))._(_('0')))).toBe(true); + }); + + it('"0" eq "a" is False', () => { + expect(eq).toDeferEvaluationOf(_('0'), _('a')); + expect($b(eq._(_('0'))._(_('a')))).toBe(false); + }); + + it('"a" eq "a" is True', () => { + expect(eq).toDeferEvaluationOf(_('a'), _('a')); + expect($b(eq._(_('a'))._(_('a')))).toBe(true); + }); + + it('"a" eq "z" is True', () => { + expect(eq).toDeferEvaluationOf(_('a'), _('z')); + expect($b(eq._(_('a'))._(_('z')))).toBe(false); + }); + }); +}); diff --git a/test/list.spec.ts b/test/list.spec.ts index b0ccd45..b290a74 100644 --- a/test/list.spec.ts +++ b/test/list.spec.ts @@ -1,9 +1,8 @@ import { describe, expect, it } from '@jest/globals'; -import { L, _, toNative } from '../src/core'; +import { L, _, toNative, undef } 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'; - +import { toArray as $a, toNumberArray as $na, Cons, List, Nil, fromArray as _a, fromNumberArray as _na, all, any, concat, cons, 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); @@ -37,6 +36,48 @@ describe('List', () => { }); }); + describe('cons', () => { + it('0 cons Nil is [0]', () => { + expect(cons).toDeferEvaluationOf(_n(0), Nil); + expect($na(cons._(_n(0))._(Nil))).toEqual([0]); + }); + + it('0 cons [1] is [0, 1]', () => { + expect(cons).toDeferEvaluationOf(_n(0), _na([1])); + expect($na(cons._(_n(0))._(_na([1])))).toEqual([0, 1]); + }); + + it('0 cons [1, 2] is [0, 1, 2]', () => { + expect(cons).toDeferEvaluationOf(_n(0), _na([1, 2])); + expect($na(cons._(_n(0))._(_na([1, 2])))).toEqual([0, 1, 2]); + }); + + it('handles infinite lists', () => { + expect($n(cons._(_n(0))._(infinite)._(undef)._(_(x => _(_xs => x))))).toBe(0); + }); + }); + + describe('concat', () => { + it('Nil concat Nil is Nil', () => { + expect(concat).toDeferEvaluationOf(Nil, Nil); + expect($na(concat._(Nil)._(Nil))).toEqual([]); + }); + + it('[0] concat [1] is [0, 1]', () => { + expect(concat).toDeferEvaluationOf(_na([0]), _na([1])); + expect($na(concat._(_na([0]))._(_na([1])))).toEqual([0, 1]); + }); + + it('[0, 1] concat [2, 3] is [0, 1, 2, 3]', () => { + expect(concat).toDeferEvaluationOf(_na([0, 1]), _na([2, 3])); + expect($na(concat._(_na([0, 1]))._(_na([2, 3])))).toEqual([0, 1, 2, 3]); + }); + + it('handles infinite lists', () => { + expect($n(concat._(infinite)._(infinite)._(undef)._(_(x => _(_xs => x))))).toBe(0); + }); + }); + describe('head', () => { it('returns nothing from Nil', () => { expect(() => head._(Nil)).not.toThrow(); diff --git a/test/pair.spec.ts b/test/pair.spec.ts new file mode 100644 index 0000000..c926b3e --- /dev/null +++ b/test/pair.spec.ts @@ -0,0 +1,54 @@ +import { describe, expect, it } from '@jest/globals'; +import { undef } from '../src/core'; +import { toBoolean as $b, fromBoolean as _b, not } from '../src/bool'; +import { toNumber as $n, fromNumber as _n, succ } from '../src/num'; +import { Pair, both, first, fst, second, snd } from '../src/pair'; + +describe('Pair', () => { + describe('fst', () => { + it('returns first', () => { + expect($b(fst._(Pair._(_b(false))._(_n(0))))).toBe(false); + }); + + it('ignores second', () => { + expect($b(fst._(Pair._(_b(false))._(undef)))).toBe(false); + }); + }); + + describe('snd', () => { + it('returns second', () => { + expect($n(snd._(Pair._(_b(false))._(_n(0))))).toBe(0); + }); + + it('ignores first', () => { + expect($n(snd._(Pair._(undef)._(_n(0))))).toBe(0); + }); + }); + + describe('first', () => { + it('updates first', () => { + expect($b(fst._(first._(not)._(Pair._(_b(false))._(_n(0)))))).toBe(true); + }); + + it('ignores second', () => { + expect($b(fst._(first._(not)._(Pair._(_b(false))._(undef))))).toBe(true); + }); + }); + + describe('second', () => { + it('updates second', () => { + expect($n(snd._(second._(succ)._(Pair._(_b(false))._(_n(0)))))).toBe(1); + }); + + it('ignores first', () => { + expect($n(snd._(second._(succ)._(Pair._(undef)._(_n(0)))))).toBe(1); + }); + }); + + describe('both', () => { + it('updates both', () => { + expect($b(fst._(both._(not)._(succ)._(Pair._(_b(false))._(_n(0)))))).toBe(true); + expect($n(snd._(both._(not)._(succ)._(Pair._(_b(false))._(_n(0)))))).toBe(1); + }); + }); +}); diff --git a/test/string.spec.ts b/test/string.spec.ts new file mode 100644 index 0000000..8788ff0 --- /dev/null +++ b/test/string.spec.ts @@ -0,0 +1,106 @@ +import { describe, expect, it } from '@jest/globals'; +import { L, _, undef } from '../src/core'; +import { toBoolean as $b } from '../src/bool'; +import { toChar as $c, fromChar as _c, eq as eqc } from '../src/char'; +import { toString as $s, Cons, Empty, String, fromString as _s, concat, eq } from '../src/string'; + +describe('String', () => { + const infinite: String = new L(() => Cons._(_c('a'))._(infinite).value);; + + describe('toString', () => { + it('converts ""', () => { + expect($s(Empty)).toBe(''); + }); + + it('converts "a"', () => { + expect($s(Cons._(_c('a'))._(Empty))).toBe('a'); + }); + + it('converts "abc"', () => { + expect($s(Cons._(_c('a'))._(Cons._(_c('b'))._(Cons._(_c('c'))._(Empty))))).toBe('abc'); + }); + }); + + describe('fromString', () => { + it('converts ""', () => { + expect($s(_s(''))).toBe(''); + }); + + it('converts "a"', () => { + expect($s(_s('a'))).toBe('a'); + }); + + it('converts "2"', () => { + expect($s(_s('abc'))).toBe('abc'); + }); + }); + + describe('concat', () => { + it('"" concat "" is ""', () => { + expect(concat).toDeferEvaluationOf(Empty, Empty); + expect($s(concat._(Empty)._(Empty))).toBe(''); + }); + + it('"a" concat "b" is "ab"', () => { + expect(concat).toDeferEvaluationOf(_s('a'), _s('b')); + expect($s(concat._(_s('a'))._(_s('b')))).toBe('ab'); + }); + + it('"abc" concat "def" is "abcdef"', () => { + expect(concat).toDeferEvaluationOf(_s('abc'), _s('def')); + expect($s(concat._(_s('abc'))._(_s('def')))).toBe('abcdef'); + }); + + it('handles infinite lists', () => { + expect($c(concat._(infinite)._(infinite)._(undef)._(_(x => _(_xs => x))))).toBe('a'); + }); + }); + + describe('eq', () => { + it('"" eq "" is True', () => { + expect(eq).toDeferEvaluationOf(eqc, _s(''), _s('')); + expect($b(eq._(_s(''))._(_s('')))).toBe(true); + }); + + it('"" eq "abc" is False', () => { + expect(eq).toDeferEvaluationOf(eqc, _s(''), _s("abc")); + expect($b(eq._(_s(''))._(_s("abc")))).toBe(false); + }); + + it('"abc" eq "" is False', () => { + expect(eq).toDeferEvaluationOf(eqc, _s("abc"), _s('')); + expect($b(eq._(_s("abc"))._(_s('')))).toBe(false); + }); + + it('"a" eq "abc" is False', () => { + expect(eq).toDeferEvaluationOf(eqc, _s('a'), _s("abc")); + expect($b(eq._(_s('a'))._(_s("abc")))).toBe(false); + }); + + it('"abc" eq "a" is False', () => { + expect(eq).toDeferEvaluationOf(eqc, _s("abc"), _s('a')); + expect($b(eq._(_s("abc"))._(_s('a')))).toBe(false); + }); + + + it('"bca" eq "abc" is False', () => { + expect(eq).toDeferEvaluationOf(eqc, _s("bca"), _s("abc")); + expect($b(eq._(_s("bca"))._(_s("abc")))).toBe(false); + }); + + it('"abc" eq "bca" is False', () => { + expect(eq).toDeferEvaluationOf(eqc, _s("abc"), _s("bca")); + expect($b(eq._(_s("abc"))._(_s("bca")))).toBe(false); + }); + + it('"abc" eq "abc" is True', () => { + expect(eq).toDeferEvaluationOf(eqc, _s("abc"), _s("abc")); + expect($b(eq._(_s("abc"))._(_s("abc")))).toBe(true); + }); + + it('handles infinite lists', () => { + expect($b(eq._(infinite)._(_s("abc")))).toBe(false); + expect($b(eq._(_s("abc"))._(infinite))).toBe(false); + }); + }); +});