Skip to content

Commit

Permalink
feat: Add Serial for serialization/deserialization (#171)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikuroXina committed Apr 17, 2024
1 parent b85afcf commit 10bd666
Show file tree
Hide file tree
Showing 23 changed files with 1,918 additions and 60 deletions.
1 change: 1 addition & 0 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export * as MonadReader from "./src/reader/monad.ts";
export * as Record from "./src/record.ts";
export * as Result from "./src/result.ts";
export * as Reverse from "./src/reverse.ts";
export * as Serial from "./src/serial.ts";
export * as Seq from "./src/seq.ts";
export * as FingerTree from "./src/seq/finger-tree.ts";
export * as Star from "./src/star.ts";
Expand Down
21 changes: 21 additions & 0 deletions src/array.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import type { Get1, Hkt1 } from "./hkt.ts";
import {
type Decoder,
decU32Be,
encFoldable,
flatMapDecoder,
pureDecoder,
} from "./serial.ts";
import { type Applicative, liftA2 } from "./type-class/applicative.ts";
import { type Foldable } from "./type-class/foldable.ts";
import type { Functor } from "./type-class/functor.ts";
import type { Monad } from "./type-class/monad.ts";
import type { Reduce } from "./type-class/reduce.ts";
Expand Down Expand Up @@ -38,6 +46,8 @@ export const monad: Monad<ArrayHkt> = {
flatMap: (fn) => (t) => t.flatMap(fn),
};

export const foldable: Foldable<ArrayHkt> = { foldR };

export const traversable: Traversable<ArrayHkt> = {
...functor,
foldR,
Expand Down Expand Up @@ -90,3 +100,14 @@ export const reduce: Reduce<ArrayHkt> = {
reduceR,
reduceL,
};

export const enc = encFoldable(foldable);
export const dec = <A>(decA: Decoder<A>): Decoder<A[]> => {
const go = (l: A[]) => (lenToRead: number): Decoder<A[]> =>
lenToRead === 0
? pureDecoder(l)
: flatMapDecoder((item: A) => go([...l, item])(lenToRead - 1))(
decA,
);
return flatMapDecoder(go([]))(decU32Be());
};
10 changes: 10 additions & 0 deletions src/bool.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
import {
type Decoder,
decU8,
type Encoder,
encU8,
mapDecoder,
} from "./serial.ts";
import { fromEquality } from "./type-class/eq.ts";
import type { Monoid } from "./type-class/monoid.ts";
import { semiGroupSymbol } from "./type-class/semi-group.ts";
Expand All @@ -21,3 +28,6 @@ export const orMonoid: Monoid<boolean> = {

export const equality = (lhs: boolean, rhs: boolean): boolean => lhs === rhs;
export const eq = fromEquality(() => equality)();

export const enc = (): Encoder<boolean> => (value) => encU8(value ? 1 : 0);
export const dec = (): Decoder<boolean> => mapDecoder((v) => v !== 0)(decU8());
35 changes: 30 additions & 5 deletions src/cat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,20 @@ export interface CatT<M, CTX> {
) => CatT<M, Record<K, A> & CTX>;

/**
* Runs the computation and overwrites the context with its return value.
* Runs the computation.
*
* @param computation - The computation to run.
* @returns A new `CatT` with modified environment.
*/
readonly run: <T>(computation: Get1<M, T>) => CatT<M, T>;
readonly run: (computation: Get1<M, []>) => CatT<M, CTX>;

/**
* Runs the computation with the context.
*
* @param computation - The computation to run.
* @returns A new `CatT` with modified environment.
*/
readonly runWith: (computation: (ctx: CTX) => Get1<M, []>) => CatT<M, CTX>;

/**
* Binds a new value wrapped by the monad, calculated from `ctx` by `fn`.
Expand All @@ -75,6 +83,14 @@ export interface CatT<M, CTX> {
* @returns A reduced value.
*/
readonly finish: <R>(fn: (ctx: CTX) => R) => Get1<M, R>;

/**
* Reduces the context into a value on `M` by `fn`.
*
* @param fn - The finishing computation.
* @returns A reduced value on `M`.
*/
readonly finishM: <R>(fn: (ctx: CTX) => Get1<M, R>) => Get1<M, R>;
}

/**
Expand Down Expand Up @@ -108,7 +124,15 @@ export const catT =
)(ctx),
),
run: (computation) =>
catT(monad)(monad.flatMap(() => computation)(ctx)),
catT(monad)(
monad.flatMap((c: CTX) => monad.map(() => c)(computation))(ctx),
),
runWith: (computation) =>
catT(monad)(
monad.flatMap(
(c: CTX) => monad.map(() => c)(computation(c)),
)(ctx),
),
addMWith: <const K extends PropertyKey, A>(
key: K,
fn: (ctx: CTX) => Get1<M, A>,
Expand All @@ -120,6 +144,7 @@ export const catT =
)(ctx),
),
finish: <R>(fn: (ctx: CTX) => R) => monad.map(fn)(ctx),
finishM: (fn) => monad.flatMap(fn)(ctx),
});

/**
Expand All @@ -128,8 +153,8 @@ export const catT =
* @param monad - The monad implementation for `M`.
* @returns A new `CatT`.
*/
export const doVoidT = <M>(monad: Monad<M>): CatT<M, void> =>
catT(monad)(monad.pure(undefined));
export const doVoidT = <M>(monad: Monad<M>): CatT<M, []> =>
catT(monad)(monad.pure([]));

/**
* Creates a new `CatT` with an empty context.
Expand Down
28 changes: 28 additions & 0 deletions src/control-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@ import type { Get1 } from "./hkt.ts";
import type { Monad } from "./type-class/monad.ts";
import type { Traversable } from "./type-class/traversable.ts";
import type { TraversableMonad } from "./type-class/traversable-monad.ts";
import {
type Decoder,
decU8,
type Encoder,
encU8,
flatMapCodeM,
mapDecoder,
monadForDecoder,
} from "./serial.ts";
import { doT } from "./cat.ts";

const continueSymbol = Symbol("ControlFlowContinue");
/**
Expand Down Expand Up @@ -121,3 +131,21 @@ export const traversableMonad = <B>(): TraversableMonad<
...monad(),
...traversable(),
});

export const enc =
<B>(encB: Encoder<B>) =>
<C>(encC: Encoder<C>): Encoder<ControlFlow<B, C>> =>
(value) =>
isBreak(value)
? flatMapCodeM(() => encB(value[1]))(encU8(0))
: flatMapCodeM(() => encC(value[1]))(encU8(1));
export const dec =
<B>(decB: Decoder<B>) =>
<C>(decC: Decoder<C>): Decoder<ControlFlow<B, C>> =>
doT(monadForDecoder)
.addM("tag", decU8())
.finishM(({ tag }): Decoder<ControlFlow<B, C>> =>
tag === 0
? mapDecoder(newBreak)(decB)
: mapDecoder(newContinue)(decC)
);
14 changes: 7 additions & 7 deletions src/free.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,17 @@ Deno.test("hello language", async (t) => {
}
};

const hello: Free<HelloLangHkt, void> = liftF(functor)({
const hello: Free<HelloLangHkt, []> = liftF(functor)({
type: "Hello",
next: undefined,
next: [],
});
const hey: Free<HelloLangHkt, void> = liftF(functor)({
const hey: Free<HelloLangHkt, []> = liftF(functor)({
type: "Hey",
next: undefined,
next: [],
});
const yearsOld = (years: number): Free<HelloLangHkt, void> =>
liftF(functor)({ type: "YearsOld", years, next: undefined });
const bye: Free<HelloLangHkt, void> = liftF(functor)({ type: "Bye" });
const yearsOld = (years: number): Free<HelloLangHkt, []> =>
liftF(functor)({ type: "YearsOld", years, next: [] });
const bye: Free<HelloLangHkt, []> = liftF(functor)({ type: "Bye" });

const m = freeMonad(functor);

Expand Down
39 changes: 0 additions & 39 deletions src/free.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,26 +145,6 @@ export const ord = fromCmp(cmp);
* @param monad - The instance of `Monad` for `F`.
* @param fr - The instance of `Free`.
* @returns The reduction of `F`.
*
* # Examples
*
* ```ts
* import * as Option from "./option.ts";
* import { retract, liftF, monad } from "./free.ts";
* import { assertEquals } from "../deps.ts";
* import { catT } from "./cat.ts";
*
* const retractOption = retract(Option.monad);
* const m = monad<Option.OptionHkt>(Option.functor);
* const lift = liftF<Option.OptionHkt>(Option.monad);
* const retracted = retractOption<string>(
* catT(m)(lift(Option.some("hoge")))
* .run(lift(Option.some("fuga")))
* .run(lift<string>(Option.none()))
* .run(lift(Option.some("foo"))).ctx,
* );
* assertEquals(Option.isNone(retracted), true);
* ```
*/
export const retract =
<F>(monad: Monad<F>) => <T>(fr: Free<F, T>): Get1<F, T> => {
Expand All @@ -181,25 +161,6 @@ export const retract =
* @param fn - The function to be applied.
* @param fr - The instance of `Free`.
* @returns The reduction of `F`.
*
* # Examples
*
* ```ts
* import { iter, liftF, monad, retract } from "./free.ts";
* import { assertEquals } from "../deps.ts";
* import { catT } from "./cat.ts";
* import * as Option from "./option.ts";
*
* const iterOption = iter<Option.OptionHkt>(Option.monad)(Option.unwrap);
* const m = monad<Option.OptionHkt>(Option.functor);
* const lift = liftF<Option.OptionHkt>(Option.monad);
* const iterated = iterOption<string>(
* catT(m)(lift(Option.some("hoge")))
* .run(lift(Option.some("fuga")))
* .run(lift(Option.some("foo"))).ctx,
* );
* assertEquals(iterated, "foo");
* ```
*/
export const iter =
<F>(functor: Functor<F>) =>
Expand Down
6 changes: 6 additions & 0 deletions src/lazy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Get1, Hkt1 } from "./hkt.ts";
import { type Decoder, type Encoder, mapDecoder } from "./serial.ts";
import type { Applicative } from "./type-class/applicative.ts";
import { fromProjection as eqFromProjection } from "./type-class/eq.ts";
import type { Functor } from "./type-class/functor.ts";
Expand Down Expand Up @@ -139,3 +140,8 @@ export const traversable: Traversable<LazyHkt> = {
foldR,
traverse,
};

export const enc = <T>(encT: Encoder<T>): Encoder<Lazy<T>> => (value) =>
encT(force(value));
export const dec = <T>(decT: Decoder<T>): Decoder<Lazy<T>> =>
mapDecoder((v: T) => defer(() => v))(decT);
31 changes: 27 additions & 4 deletions src/list.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { cat } from "./cat.ts";
import type { Get1, Hkt1 } from "./hkt.ts";
import * as Option from "./option.ts";
import { isNone } from "./option.ts";
import { andThen, type Ordering } from "./ordering.ts";
import {
type Decoder,
decU32Be,
encFoldable,
flatMapDecoder,
pureDecoder,
} from "./serial.ts";
import type { Tuple } from "./tuple.ts";
import { type Applicative, liftA2 } from "./type-class/applicative.ts";
import { type Eq, fromEquality } from "./type-class/eq.ts";
import type { Foldable } from "./type-class/foldable.ts";
import type { Functor } from "./type-class/functor.ts";
import type { Monad } from "./type-class/monad.ts";
import type { Monoid } from "./type-class/monoid.ts";
Expand All @@ -29,7 +36,7 @@ export interface List<T> {

export const partialEquality = <T>(equalityT: PartialEq<T>) => {
const self = (l: List<T>, r: List<T>): boolean =>
(isNone(l.current()) && isNone(r.current())) ||
(Option.isNone(l.current()) && Option.isNone(r.current())) ||
(Option.partialEq(equalityT).eq(l.current(), r.current()) &&
self(l.rest(), r.rest()));

Expand All @@ -38,7 +45,7 @@ export const partialEquality = <T>(equalityT: PartialEq<T>) => {
export const partialEq = fromPartialEquality(partialEquality);
export const equality = <T>(equalityT: Eq<T>) => {
const self = (l: List<T>, r: List<T>): boolean =>
(isNone(l.current()) && isNone(r.current())) ||
(Option.isNone(l.current()) && Option.isNone(r.current())) ||
(Option.eq(equalityT).eq(l.current(), r.current()) &&
self(l.rest(), r.rest()));
return self;
Expand All @@ -59,7 +66,7 @@ export const cmp = <T>(order: Ord<T>) => {
);
return self;
};
export const ord = fromCmp(cmp);
export const ord = <T>(x: Ord<T>) => fromCmp(cmp)(x);

/**
* Checks whether the list has a current element.
Expand Down Expand Up @@ -1957,6 +1964,11 @@ export const monad: Monad<ListHkt> = {
concat(map((fn: (t: T1) => U1) => map(fn)(t))(fns)),
};

/**
* The instance of `Foldable` for `List`.
*/
export const foldable: Foldable<ListHkt> = { foldR };

/**
* The instance of `Traversable` for `List`.
*/
Expand All @@ -1980,3 +1992,14 @@ export const reduce: Reduce<ListHkt> = {
reduceL: foldL,
reduceR: (reducer) => (fa) => (b) => foldR(reducer)(b)(fa),
};

export const enc = () => encFoldable(foldable);
export const dec = <A>(decA: Decoder<A>): Decoder<List<A>> => {
const go = (l: List<A>) => (lenToRead: number): Decoder<List<A>> =>
lenToRead === 0
? pureDecoder(reverse(l))
: flatMapDecoder((item: A) =>
go(appendToHead(item)(l))(lenToRead - 1)
)(decA);
return flatMapDecoder(go(empty<A>()))(decU32Be());
};
14 changes: 14 additions & 0 deletions src/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ import {
import { isNone, isSome, none, Option, some } from "./option.ts";
import { Ordering } from "./ordering.ts";
import { isOk, Result } from "./result.ts";
import { Decoder, type Encoder, mapDecoder } from "./serial.ts";
import {
dec as decTuple,
enc as encTuple,
ord as tupleOrd,
partialOrd as tuplePartialOrd,
Tuple,
} from "./tuple.ts";
import { dec as decArray, enc as encArray } from "./array.ts";
import { Applicative } from "./type-class/applicative.ts";
import { fromEquality } from "./type-class/eq.ts";
import { Foldable } from "./type-class/foldable.ts";
Expand Down Expand Up @@ -737,3 +741,13 @@ export const traversable = <K>(): Traversable<Apply2Only<MapHkt, K>> => ({
foldR,
traverse,
});

export const enc =
<K>(encK: Encoder<K>) =>
<V>(encV: Encoder<V>): Encoder<Map<K, V>> =>
(value) => encArray(encTuple(encK)(encV))([...value.entries()]);
export const dec =
<K>(decK: Decoder<K>) => <V>(decV: Decoder<V>): Decoder<Map<K, V>> =>
mapDecoder((kv: Tuple<K, V>[]) => new Map(kv))(
decArray(decTuple(decK)(decV)),
);

0 comments on commit 10bd666

Please sign in to comment.