Skip to content

Commit

Permalink
fix: Fix bugs and cover Array with test (#216)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikuroXina authored May 12, 2024
1 parent 99f6eb7 commit 93084d7
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 7 deletions.
182 changes: 179 additions & 3 deletions src/array.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,90 @@
import { assertEquals } from "../deps.ts";
import { foldR, fromReduce, reduceL, reduceR, traverse } from "./array.ts";
import {
cmp,
dec,
enc,
equality,
foldR,
fromReduce,
monad,
partialCmp,
partialEquality,
reduceL,
reduceR,
traverse,
} from "./array.ts";
import { range, reduce } from "./list.ts";
import { monad, type PromiseHkt } from "./promise.ts";
import { some } from "./option.ts";
import { equal, greater, less, type Ordering } from "./ordering.ts";
import { monad as promiseMonad, type PromiseHkt } from "./promise.ts";
import { unwrap } from "./result.ts";
import { decU32Be, encU32Be, runCode, runDecoder } from "./serial.ts";
import { type Eq, eqSymbol } from "./type-class/eq.ts";
import type { Ord } from "./type-class/ord.ts";
import { strict } from "./type-class/partial-eq.ts";

Deno.test("partial equality", () => {
const partialEq = partialEquality(strict<number>());

assertEquals(partialEq([], []), true);
assertEquals(partialEq([0], [0]), true);
assertEquals(partialEq([2], [2]), true);

assertEquals(partialEq([], [1]), false);
assertEquals(partialEq([1], []), false);
assertEquals(partialEq([0], [0, 3]), false);
assertEquals(partialEq([4, 0], [0]), false);
});

const stringEq: Eq<string> = {
eq: (l, r) => l === r,
[eqSymbol]: true,
};

Deno.test("equality", () => {
const eq = equality(stringEq);

assertEquals(eq([], []), true);
assertEquals(eq(["0"], ["0"]), true);
assertEquals(eq(["2"], ["2"]), true);

assertEquals(eq([], ["1"]), false);
assertEquals(eq(["1"], []), false);
assertEquals(eq(["0"], ["0", "3"]), false);
assertEquals(eq(["4", "0"], ["0"]), false);
});

const stringOrd: Ord<string> = {
...stringEq,
cmp: (l, r) => l < r ? -1 : l === r ? 0 : 1,
partialCmp: (l, r) => some(l < r ? -1 : l === r ? 0 : 1),
};

Deno.test("partial order", () => {
const cmp = partialCmp(stringOrd);

assertEquals(cmp([], []), some<Ordering>(equal));
assertEquals(cmp(["0"], ["0"]), some<Ordering>(equal));
assertEquals(cmp(["2"], ["2"]), some<Ordering>(equal));

assertEquals(cmp([], ["1"]), some<Ordering>(less));
assertEquals(cmp(["0"], ["0", "3"]), some<Ordering>(less));
assertEquals(cmp(["1"], []), some<Ordering>(greater));
assertEquals(cmp(["4", "0"], ["0"]), some<Ordering>(greater));
});

Deno.test("total order", () => {
const totalCmp = cmp(stringOrd);

assertEquals(totalCmp([], []), equal);
assertEquals(totalCmp(["0"], ["0"]), equal);
assertEquals(totalCmp(["2"], ["2"]), equal);

assertEquals(totalCmp([], ["1"]), less);
assertEquals(totalCmp(["0"], ["0", "3"]), less);
assertEquals(totalCmp(["1"], []), greater);
assertEquals(totalCmp(["4", "0"], ["0"]), greater);
});

const sub = (next: number) => (acc: number) => next - acc;

Expand All @@ -24,12 +107,98 @@ Deno.test("foldR", () => {
});

Deno.test("traverse", async () => {
const actual = await traverse<PromiseHkt>(monad)((item: string) =>
const actual = await traverse<PromiseHkt>(promiseMonad)((item: string) =>
Promise.resolve(item.length)
)(["foo", "hoge"]);
assertEquals(actual, [3, 4]);
});

Deno.test("functor", () => {
const data = [1, 4, 2, 3, 5, 2, 3];
// identity
assertEquals(
monad.map((x: number) => x)(data),
data,
);

// composition
const alpha = (x: number) => x + 3;
const beta = (x: number) => x * 4;
assertEquals(
monad.map((x: number) => beta(alpha(x)))(data),
monad.map(beta)(monad.map(alpha)(data)),
);
});

Deno.test("applicative", () => {
type NumToNum = (i: number) => number;
// identity
{
const x = [1, 4, 2, 3, 5, 2, 3];
assertEquals(monad.apply(monad.pure((i: number) => i))(x), x);
}

// composition
{
const x = [(i: number) => i + 3, (i: number) => i * 4];
const y = [(i: number) => i + 5, (i: number) => i * 3];
const z = [1, 4, 2, 3, 5, 2, 3];
assertEquals(
monad.apply(
monad.apply(
monad.apply(
monad.pure(
(f: NumToNum) =>
(g: NumToNum): NumToNum =>
(i: number) => f(g(i)),
),
)(x),
)(y),
)(z),
monad.apply(x)(monad.apply(y)(z)),
);
}

// homomorphism
{
const x = 42;
const add = (x: number) => [x + 1, x + 2, x + 3];
assertEquals(
monad.apply(monad.pure(add))(monad.pure(x)),
monad.pure(add(x)),
);
}

// interchange
{
const f = [(i: number) => i + 3, (i: number) => i * 4];
const x = 42;
assertEquals(
monad.apply(f)(monad.pure(x)),
monad.apply(monad.pure((i: NumToNum) => i(x)))(f),
);
}
});

Deno.test("monad", () => {
const data = [1, 4, 2, 3, 5, 2, 3];
const add = (x: number) => [x + 1, x + 2, x + 3];
const mul = (x: number) => [x * 2, x * 3, x * 4];

// left identity
assertEquals(monad.flatMap(add)(monad.pure(1)), add(1));
assertEquals(monad.flatMap(mul)(monad.pure(1)), mul(1));

// right identity
assertEquals(monad.flatMap(monad.pure)(data), data);

// associativity
assertEquals(
monad.flatMap(add)(monad.flatMap(mul)(data)),
monad.flatMap((x: number) => monad.flatMap(add)(mul(x)))(data),
);
});

Deno.test("fromReduce", () => {
const actual = fromReduce(reduce)(range(0, 4));
assertEquals(actual, [0, 1, 2, 3]);
Expand All @@ -48,3 +217,10 @@ Deno.test("reduceL", () => {
const actual = reduceL(sub)(0)([1, 4, 2, 3, 5, 2, 3]);
assertEquals(actual, -20);
});

Deno.test("encode then decode", async () => {
const data = [1, 4, 2, 3, 5, 2, 3];
const code = await runCode(enc(encU32Be)(data));
const decoded = unwrap(runDecoder(dec(decU32Be()))(code));
assertEquals(decoded, data);
});
2 changes: 1 addition & 1 deletion src/serial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1299,7 +1299,7 @@ const splitAt = (firstLen: number) =>
const src = new Uint8Array(data.buffer);
const leftBuf = new ArrayBuffer(Math.min(firstLen, data.byteLength));
const rightBuf = new ArrayBuffer(Math.max(data.byteLength - firstLen, 0));
new Uint8Array(leftBuf).set(src);
new Uint8Array(leftBuf).set(src.slice(0, firstLen));
new Uint8Array(rightBuf).set(src.slice(firstLen));
return [new DataView(leftBuf), new DataView(rightBuf)];
};
Expand Down
10 changes: 10 additions & 0 deletions src/type-class/applicative.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ import type { Monoid } from "./monoid.ts";
import type { Pure } from "./pure.ts";
import { semiGroupSymbol } from "./semi-group.ts";

/**
* A functor with application. It can combine sequence computations with `apply` or `liftA2` function.
*
* All instances of the applicative `a` must satisfy the following laws:
*
* - Identity: For all `x`; `a.apply(a.pure((i) => i))(x)` equals to `x`,
* - Composition: For all `x`, `y` and `z`; `a.apply(a.apply(a.apply(a.pure((f) => (g) => (i) => f(g(i))))(x))(y))(z)` equals to `a.apply(x)(a.apply(y)(z))`,
* - Homomorphism: For all `f` and `x`; `a.apply(a.pure(f))(a.pure(x))` equals to `a.pure(f(x))`,
* - Interchange: For all `f` and `x`; `a.apply(f)(a.pure(x))` equals to `a.apply(a.pure((i) => i(x)))(f)`.
*/
export interface Applicative<S> extends Apply<S>, Pure<S> {}

export const makeMonoid = <S>(
Expand Down
6 changes: 3 additions & 3 deletions src/type-class/functor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import type { Invariant } from "./variance.ts";
/**
* A structure which able to lift up in `F`.
*
* An instance of `Functor<F>` must satisfy following rules:
* All instances of the functor `f` must satisfy the following laws:
*
* - Identity: `map(id) == id`,
* - Composition: for all `f` and `g`; `map(pipe(f)(g)) == pipe(map(f))(map(g))`.
* - Identity: `f.map((x) => x)` equals to `(x) => x`,
* - Composition: For all `a` and `b`; `f.map((x) => b(a(x)))` equals to `(x) => f.map(b)(f.map(a)(x))`.
*/
export interface Functor<F> {
/**
Expand Down
7 changes: 7 additions & 0 deletions src/type-class/monad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import { monad as idMonad } from "../identity.ts";
import type { Applicative } from "./applicative.ts";
import type { FlatMap } from "./flat-map.ts";

/**
* A sequential computation framework with wrapping the type `S`. All instances of the monad `m` must satisfy the following laws:
*
* - Left identity: For all `f` and `a`; `m.flatMap(f)(m.pure(a))` equals to `f(a)`,
* - Right identity: For all `a`; `m.flatMap(m.pure)(a)` equals to `a`,
* - Associativity: For all `f`, `g` and `a`; `m.flatMap(f)(m.flatMap(g)(a))` equals to `m.flatMap((x) => m.flatMap(f)(g(x)))(a)`.
*/
export interface Monad<S> extends Applicative<S>, FlatMap<S> {}

export const flat = <S>(
Expand Down

0 comments on commit 93084d7

Please sign in to comment.