Skip to content

Commit

Permalink
fix: Add docs and Improve filter func (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
MikuroXina authored Sep 20, 2023
1 parent 5c7b1af commit b8629d1
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 14 deletions.
76 changes: 68 additions & 8 deletions src/optical.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,36 @@ export * as Lens from "./optical/lens.js";
export * as Prism from "./optical/prism.js";
export * as Setter from "./optical/setter.js";

/**
* Computation combinator with two-terminal pair.
* ```text
* |---------------|
* S ->| |-> A
* | Computation |
* T <-| |<- B
* |---------------|
* ```
*/
export type Optic<in S, out T, out A, in B> = <R>(
next: (sending: A) => (continuation: (returned: B) => R) => R,
) => (received: S) => (callback: (t: T) => R) => R;
export type OpticSimple<S, A> = Optic<S, S, A, A>;

/**
* The identity combinator which does nothing.
*/
export const identity =
<S>(): Optic<S, S, S, S> =>
(x) =>
x;

/**
* Composes two computations.
*
* @param left - The second process.
* @param right - The first process.
* @returns The composed computation.
*/
export const compose =
<X, Y, S, T>(left: Optic<X, Y, S, T>) =>
<A, B>(right: Optic<S, T, A, B>): Optic<X, Y, A, B> =>
Expand Down Expand Up @@ -59,14 +79,55 @@ export const unwrap =
});

export interface OpticCat<S, T, A, B> {
/**
* Feeds the `Optic` and produces a new environment.
*
* @param o - The computation such as `Lens`, `Prism` and so on.
* @returns Modified environment.
*/
readonly feed: <X, Y>(o: Optic<A, B, X, Y>) => OpticCat<S, T, X, Y>;
/**
* Modifies the value of the focused entry.
*
* @param modifier - The function which maps from the entry value to you desired.
* @returns Whole of data with the entry modified.
*/
readonly over: (modifier: (a: A) => B) => T;
/**
* Overwrites the value of the focused entry.
*
* @param value - The value to be placed.
* @returns Whole of data with `value`.
*/
readonly set: (value: B) => T;
/**
* Overwrites the value with the modifying computation.
*
* @param setter - The finish computation to add.
* @returns Whole of data with `setter`.
*/
readonly setWith: (setter: Setter<A, B>) => T;
/**
* Extracts the value of the focused entry.
*
* @returns Extracted value if exists.
*/
readonly get: () => Option<A>;
/**
* Extracts the value of the focused entry, or throws an error if not found.
*
* @returns Extracted value.
*/
readonly unwrap: () => A;
}

/**
* Creates a focused environment to compute about the part of the data structure.
*
* @param data - The data to be computed.
* @param o - The computation to use.
* @returns The modified environment.
*/
export const focused =
<S>(data: S) =>
<T, A, B>(o: Optic<S, T, A, B>): OpticCat<S, T, A, B> => ({
Expand All @@ -81,11 +142,10 @@ export const focused =
}),
});

export const opticCat = <S>(data: S): OpticCat<S, S, S, S> => ({
feed: (o) => focused(data)(compose(identity<S>())(o)),
over: (modifier) => modifier(data),
set: (value) => value,
setWith: (setter) => setter(() => () => data)(data)((s) => s),
get: () => some(data),
unwrap: () => data,
});
/**
* Creates an environment to compute about the data structure.
*
* @param data - The data to be computed.
* @returns The environment to compute.
*/
export const opticCat = <S>(data: S): OpticCat<S, S, S, S> => focused(data)(identity());
25 changes: 25 additions & 0 deletions src/optical/lens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@

import { type Optic } from "../optical.js";

/**
* Creates a new `Lens` optic from the two functions.
*
* @param get - The extraction process.
* @param set - The overwrite process.
* @returns The computation to focus the data.
*/
export const newLens =
<S, A>(get: (s: S) => A) =>
<B, T>(set: (s: S) => (b: B) => T): Optic<S, T, A, B> =>
Expand All @@ -18,6 +25,12 @@ export const newLens =
(callback) =>
next(get(received))((b) => callback(set(received)(b)));

/**
* Focuses to the given index of array.
*
* @param index - The index of array to extract.
* @returns The lens for indexing.
*/
export const nth = <const I extends number, Tuple extends readonly unknown[], V = Tuple[I]>(
index: I,
): Optic<Tuple, Tuple, Tuple[I], V> =>
Expand All @@ -26,6 +39,12 @@ export const nth = <const I extends number, Tuple extends readonly unknown[], V
[...source.slice(0, index), part, ...source.slice(index + 1)] as unknown as Tuple,
);

/**
* Focuses to the given key of object.
*
* @param k - The key of object to extract.
* @returns The lens for indexing.
*/
export const key = <const K extends PropertyKey, O extends Readonly<Record<K, unknown>>, V = O[K]>(
k: K,
): Optic<O, O, O[K], V> =>
Expand All @@ -39,6 +58,12 @@ export type Entries<O, K> = K extends readonly [infer H, ...infer R]
: never
: [PropertyKey, unknown][];

/**
* Focuses to the given keys of object.
*
* @param k - The keys array of object to extract.
* @returns The lens for indexing.
*/
export const keys = <
const K extends readonly PropertyKey[],
O extends Readonly<Record<K[number], unknown>>,
Expand Down
39 changes: 33 additions & 6 deletions src/optical/prism.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ import type { Optic } from "../optical.js";
import { none, okOr, type Option, some } from "../option.js";
import { either, err, type Result } from "../result.js";

/**
* Creates a new `Prism` optic from the two functions.
*
* @param upcast - The function which coerces the modified value to a partial type.
* @param downcast - The function which tries to coerces the source value.
* @returns The computation to focus the data.
*/
export const newPrism =
<B, T>(upcast: (b: B) => T) =>
<S, A>(downcast: (s: S) => Result<T, A>): Optic<S, T, A, B> =>
Expand All @@ -24,17 +31,37 @@ export const newPrism =
(callback) =>
either(callback)((a: A) => next(a)((b) => callback(upcast(b))))(downcast(received));

/**
* Creates a new `Prism` optic from the two functions, but `downcast` may return a `Option`.
*
* @param upcast - The function which coerces the modified value to a partial type.
* @param downcast - The function which tries to coerces the source value.
* @returns The computation to focus the data.
*/
export const newPrismSimple =
<B, S>(upcast: (b: B) => S) =>
<A>(downcast: (s: S) => Option<A>): Optic<S, S, A, B> =>
newPrism(upcast)((s) => okOr(s)(downcast(s)));

/**
* @returns The optic which matches nothing. Getting a value through this will throw an error.
*/
export const unreachable = <S, A>(): Optic<S, S, A, never> => newPrism<never, S>(absurd)<S, A>(err);

export const only = <A>(target: A): Optic<A, A, A, []> =>
newPrismSimple<[], A>(([]) => target)((x) => (x === target ? some(x) : none()));
/**
* Filters the value only if equals to `target`.
*
* @param target - For comparison.
* @returns The computation to filter the data.
*/
export const only = <A>(target: A): Optic<A, A, A, A> =>
newPrismSimple<A, A>((newValue) => newValue)((x) => (x === target ? some(x) : none()));

export const filter =
<A>(init: A) =>
(pred: (a: A) => boolean): Optic<A, A, A, []> =>
newPrismSimple<[], A>(([]) => init)((x) => (pred(x) ? some(x) : none()));
/**
* Filters the value only if satisfies `pred`.
*
* @param pred - Condition to filter.
* @returns The computation to filter the data.
*/
export const filter = <A>(pred: (a: A) => boolean): Optic<A, A, A, A> =>
newPrismSimple<A, A>((newValue) => newValue)((x) => (pred(x) ? some(x) : none()));
21 changes: 21 additions & 0 deletions src/optical/setter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,30 @@ import type { Optic } from "../optical.js";
import type { Functor } from "../type-class/functor.js";
import type { Monad } from "../type-class/monad.js";

/**
* `Setter` is a `Optic` but does not allow to compose any computations more.
*/
export type Setter<S, T> = Optic<S, T, never, never>;

/**
* Modifies going data as a terminal.
*
* @param mapper - The function to map the data going.
* @returns The mapping optic like `over`.
*/
export const set =
<S, T>(mapper: (s: S) => T): Setter<S, T> =>
() =>
(received) =>
(callback) =>
callback(mapper(received));

/**
* Modifies data contained by `Functor` as a terminal.
*
* @param mapper - The function to map the data going.
* @returns The mapping optic.
*/
export const setF =
<F>(f: Functor<F>) =>
<S, T>(mapper: (s: S) => T): Setter<Get1<F, S>, Get1<F, T>> =>
Expand All @@ -30,6 +45,12 @@ export const setF =
(callback) =>
callback(f.map(mapper)(received));

/**
* Modifies data contained by `Monad` as a terminal.
*
* @param mapper - The function to map the data going.
* @returns The mapping optic.
*/
export const setM =
<M>(m: Monad<M>) =>
<S, T>(mapper: (s: S) => T): Setter<Get1<M, S>, Get1<M, T>> =>
Expand Down

0 comments on commit b8629d1

Please sign in to comment.