Skip to content

Commit

Permalink
feat: Add encSum and decSum (#177)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikuroXina committed Apr 23, 2024
1 parent 8c31c3c commit 97340eb
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 65 deletions.
24 changes: 10 additions & 14 deletions src/control-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,13 @@ import type { Traversable } from "./type-class/traversable.ts";
import type { TraversableMonad } from "./type-class/traversable-monad.ts";
import {
type Decoder,
decSum,
decU8,
type Encoder,
encSum,
encU8,
flatMapCodeM,
mapDecoder,
monadForDecoder,
} from "./serial.ts";
import { doT } from "./cat.ts";
import type { Bifunctor } from "./type-class/bifunctor.ts";

const continueSymbol = Symbol("ControlFlowContinue");
Expand Down Expand Up @@ -138,17 +137,14 @@ export const bifunctor: Bifunctor<ControlFlowHkt> = { biMap };
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));
encSum({
[breakSymbol]: ([, b]: Break<B>) => encB(b),
[continueSymbol]: ([, c]: Continue<C>) => encC(c),
})(([key]) => key)((key) => encU8(key === breakSymbol ? 0 : 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)
);
decSum(decU8())<ControlFlow<B, C>>([
mapDecoder(newBreak)(decB),
mapDecoder(newContinue)(decC),
]);
19 changes: 9 additions & 10 deletions src/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,20 @@
*
* @packageDocumentation
*/
import { doT } from "./cat.ts";
import type { Get1, Hkt1 } from "./hkt.ts";
import type { Optic, OpticSimple } from "./optical.ts";
import { newPrism, newPrismSimple } from "./optical/prism.ts";
import { equal, greater, less, type Ordering } from "./ordering.ts";
import { err, isOk, ok, type Result } from "./result.ts";
import {
type Decoder,
decSum,
decU8,
type Encoder,
encSum,
encU8,
flatMapCodeM,
encUnit,
mapDecoder,
monadForDecoder,
pureDecoder,
} from "./serial.ts";
import type { Applicative } from "./type-class/applicative.ts";
Expand Down Expand Up @@ -835,11 +835,10 @@ export const ifNone = <T>(): OpticSimple<Option<T>, void> =>
mapOrElse<Option<void>>(() => some(undefined))(none),
);

export const enc = <T>(encT: Encoder<T>): Encoder<Option<T>> => (value) =>
isNone(value) ? encU8(0) : flatMapCodeM(() => encT(value[1]))(encU8(1));
export const enc = <T>(encT: Encoder<T>): Encoder<Option<T>> =>
encSum({
[noneSymbol]: (_value: None) => encUnit([]),
[someSymbol]: (value: Some<T>) => encT(value[1]),
})(([key]) => key)((type) => encU8(type === noneSymbol ? 0 : 1));
export const dec = <T>(decT: Decoder<T>): Decoder<Option<T>> =>
doT(monadForDecoder)
.addM("tag", decU8())
.finishM(({ tag }): Decoder<Option<T>> =>
tag === 0 ? pureDecoder(none()) : mapDecoder(some)(decT)
);
decSum(decU8())<Option<T>>([pureDecoder(none()), mapDecoder(some)(decT)]);
25 changes: 11 additions & 14 deletions src/result.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { doT } from "./cat.ts";
import type { Apply2Only, Get1, Hkt2 } from "./hkt.ts";
import type { Optic } from "./optical.ts";
import { newPrism } from "./optical/prism.ts";
Expand All @@ -12,12 +11,12 @@ import {
import { greater, less, type Ordering } from "./ordering.ts";
import {
Decoder,
decSum,
decU8,
Encoder,
encSum,
encU8,
flatMapCodeM,
mapDecoder,
monadForDecoder,
} from "./serial.ts";
import type { Applicative } from "./type-class/applicative.ts";
import type { Bifoldable } from "./type-class/bifoldable.ts";
Expand Down Expand Up @@ -712,16 +711,14 @@ export const ifOk = <E, T, U>(): Optic<Result<E, T>, Result<E, U>, T, U> =>
);

export const enc =
<E>(encE: Encoder<E>) =>
<T>(encT: Encoder<T>): Encoder<Result<E, T>> =>
(value) =>
isErr(value)
? flatMapCodeM(() => encE(value[1]))(encU8(0))
: flatMapCodeM(() => encT(value[1]))(encU8(1));
<E>(encE: Encoder<E>) => <T>(encT: Encoder<T>): Encoder<Result<E, T>> =>
encSum({
[errSymbol]: ([, err]: Err<E>) => encE(err),
[okSymbol]: ([, ok]: Ok<T>) => encT(ok),
})(([key]) => key)((type) => encU8(type === errSymbol ? 0 : 1));
export const dec =
<E>(decE: Decoder<E>) => <T>(decT: Decoder<T>): Decoder<Result<E, T>> =>
doT(monadForDecoder)
.addM("tag", decU8())
.finishM(({ tag }): Decoder<Result<E, T>> =>
tag === 0 ? mapDecoder(err)(decE) : mapDecoder(ok)(decT)
);
decSum(decU8())<Result<E, T>>([
mapDecoder(err)(decE),
mapDecoder(ok)(decT),
]);
69 changes: 69 additions & 0 deletions src/serial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,15 @@ export type Code = CodeM<[]>;
*/
export type Encoder<T> = (value: T) => Code;

export interface EncoderHkt extends Hkt1 {
readonly type: Encoder<this["arg1"]>;
}

/**
* Retrieves the encoding type from an `Encoder`.
*/
export type Encoding<E> = E extends Encoder<infer T> ? T : never;

/**
* A `Monoid` instance for `Code`.
*/
Expand Down Expand Up @@ -685,6 +694,11 @@ export const monadForCodeM: Monad<CodeMHkt> = {
*/
export const flushCode: Code = tell(flush);

/**
* Encodes nothing. It is an identity encoder.
*/
export const encUnit: Encoder<[]> = compose(tell)(() => empty);

/**
* Encodes a number as a signed 8-bit integer.
*/
Expand Down Expand Up @@ -769,6 +783,40 @@ export const encFoldable =
)(data),
);

/**
* Encodes a sum type value with encoders and functions for index key.
*
* @param keyExtractor - A function that extracts the index key of `variantEncoders` from a sum type value.
* @param keyEncoder - An encoder for the index key value of a sum type value.
* @param variantEncoders - Encoders for each variant of the sum type.
* @returns The encoder for the sum type.
*/
export const encSum = <
O extends object,
K extends PropertyKey = keyof O,
T = Encoding<O[keyof O]>,
>(
variantEncoders: O,
) =>
(keyExtractor: (value: T) => K) =>
(keyEncoder: Encoder<K>): Encoder<T> =>
(value) => {
const key = keyExtractor(value);
if (!Object.hasOwn(variantEncoders, key)) {
throw new Error(
`entry of key was not owned by the variantEncoders`,
);
}
return doT(monadForCodeM)
.run(keyEncoder(key))
.run(
(variantEncoders[key as unknown as keyof O] as Encoder<T>)(
value,
),
)
.finish(() => []);
};

/**
* A result that reports how many bytes has the data read.
*/
Expand Down Expand Up @@ -1504,3 +1552,24 @@ export const decUtf8 = (): Decoder<string> =>
.addM("len", decU32Be())
.addMWith("bytes", ({ len }) => decBytes(len))
.finish(({ bytes }) => new TextDecoder().decode(bytes));

/**
* Decodes a sum type value with the `keyDecoder` and `decoders`.
*
* @param keyDecoder - A decoder that decodes the index key of the sum type.
* @param variantDecoders - A table of decoders for each variant of the sum type.
* @returns The sum type decoder.
*/
export const decSum =
<K extends PropertyKey>(keyDecoder: Decoder<K>) =>
<T>(variantDecoders: Record<K, Decoder<T>>): Decoder<T> =>
doT(monadForDecoder)
.addM("key", keyDecoder)
.when(
({ key }) => !Object.hasOwn(variantDecoders, key),
() =>
failDecoder(
"entry of key was not owned by the variantDecoders",
),
)
.finishM(({ key }) => variantDecoders[key]);
50 changes: 23 additions & 27 deletions src/these.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { id } from "./identity.ts";
import { appendToHead, either, empty, type List } from "./list.ts";
import {
Decoder,
decSum,
decU8,
Encoder,
encSum,
encU8,
mapDecoder,
monadForCodeM,
Expand Down Expand Up @@ -535,32 +537,26 @@ export const monad = <A>(
});

export const enc =
<A>(encA: Encoder<A>) =>
<B>(encB: Encoder<B>): Encoder<These<A, B>> =>
(value) =>
isThis(value)
? doT(monadForCodeM)
.run(encU8(0))
.finishM(() => encA(value[1]))
: isThat(value)
? doT(monadForCodeM)
.run(encU8(1))
.finishM(() => encB(value[1]))
: doT(monadForCodeM)
.run(encU8(2))
.run(encA(value[1]))
.finishM(() => encB(value[2]));
<A>(encA: Encoder<A>) => <B>(encB: Encoder<B>): Encoder<These<A, B>> =>
encSum({
[thisSymbol]: ([, a]: This<A>) => encA(a),
[thatSymbol]: ([, b]: That<B>) => encB(b),
[bothSymbol]: ([, a, b]: Both<A, B>) =>
doT(monadForCodeM)
.run(encA(a))
.finishM(() => encB(b)),
})(([key]) => key)((key) =>
encU8(
key === thisSymbol ? 0 : (key === thatSymbol ? 1 : 2),
)
);
export const dec =
<A>(decA: Decoder<A>) => <B>(decB: Decoder<B>): Decoder<These<A, B>> =>
doT(monadForDecoder)
.addM("tag", decU8())
.finishM(({ tag }): Decoder<These<A, B>> =>
tag === 0
? mapDecoder(newThis)(decA)
: tag === 1
? mapDecoder(newThat)(decB)
: doT(monadForDecoder)
.addM("left", decA)
.addM("right", decB)
.finish(({ left, right }) => newBoth(left)(right))
);
decSum(decU8())<These<A, B>>([
mapDecoder(newThis)(decA),
mapDecoder(newThat)(decB),
doT(monadForDecoder)
.addM("left", decA)
.addM("right", decB)
.finish(({ left, right }) => newBoth(left)(right)),
]);

0 comments on commit 97340eb

Please sign in to comment.