summaryrefslogtreecommitdiff
path: root/lib/types/fn
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-07-27 17:03:10 -0700
committerElizabeth Hunt <me@liz.coffee>2025-07-27 18:30:30 -0700
commit9970036d203ba2d0a46b35ba6fad21d49441cdd4 (patch)
treea585d13933bf4149dcb07e28526063d071453105 /lib/types/fn
downloadpengueno-9970036d203ba2d0a46b35ba6fad21d49441cdd4.tar.gz
pengueno-9970036d203ba2d0a46b35ba6fad21d49441cdd4.zip
hai
Diffstat (limited to 'lib/types/fn')
-rw-r--r--lib/types/fn/callable.ts21
-rw-r--r--lib/types/fn/either.ts143
-rw-r--r--lib/types/fn/index.ts3
-rw-r--r--lib/types/fn/optional.ts93
4 files changed, 260 insertions, 0 deletions
diff --git a/lib/types/fn/callable.ts b/lib/types/fn/callable.ts
new file mode 100644
index 0000000..60d747b
--- /dev/null
+++ b/lib/types/fn/callable.ts
@@ -0,0 +1,21 @@
+export interface Callable<T = any, ArgT = any> {
+ (...args: Array<ArgT>): T;
+}
+
+export interface Supplier<T> extends Callable<T, undefined> {
+ (): T;
+}
+
+export interface Mapper<T, U> extends Callable<U, T> {
+ (t: T): U;
+}
+
+export interface Predicate<T> extends Mapper<T, boolean> {}
+
+export interface BiMapper<T, U, R> extends Callable {
+ (t: T, u: U): R;
+}
+
+export interface SideEffect<T> extends Mapper<T, void> {}
+
+export interface BiSideEffect<T, U> extends BiMapper<T, U, void> {}
diff --git a/lib/types/fn/either.ts b/lib/types/fn/either.ts
new file mode 100644
index 0000000..0f65859
--- /dev/null
+++ b/lib/types/fn/either.ts
@@ -0,0 +1,143 @@
+import {
+ BiMapper,
+ IOptional,
+ type Mapper,
+ Optional,
+ Predicate,
+ type Supplier,
+ Tagged,
+ isTagged,
+} from '@emprespresso/pengueno';
+
+export const IEitherTag = 'IEither' as const;
+export type IEitherTag = typeof IEitherTag;
+export const isEither = <E, T>(o: unknown): o is IEither<E, T> => isTagged(o, IEitherTag);
+export interface IEither<E, T> extends Tagged<IEitherTag> {
+ readonly left: Supplier<IOptional<E>>;
+ readonly right: Supplier<IOptional<T>>;
+
+ readonly mapRight: <_T>(mapper: Mapper<T, _T>) => IEither<E, _T>;
+ readonly filter: (mapper: Predicate<T>) => IEither<E, T>;
+ readonly mapLeft: <_E>(mapper: Mapper<E, _E>) => IEither<_E, T>;
+ readonly mapBoth: <_E, _T>(errBranch: Mapper<E, _E>, okBranch: Mapper<T, _T>) => IEither<_E, _T>;
+
+ readonly flatMap: <_T>(mapper: Mapper<T, IEither<E, _T>>) => IEither<E, _T>;
+ readonly flatMapAsync: <_T>(mapper: Mapper<T, Promise<IEither<E, _T>>>) => Promise<IEither<E, _T>>;
+
+ readonly moveRight: <_T>(t: _T) => IEither<E, _T>;
+ readonly fold: <_T>(leftFolder: Mapper<E, _T>, rightFolder: Mapper<T, _T>) => _T;
+ readonly joinRight: <O, _T>(other: IEither<E, O>, mapper: (a: O, b: T) => _T) => IEither<E, _T>;
+ readonly joinRightAsync: <O, _T>(
+ other: (() => Promise<IEither<E, O>>) | Promise<IEither<E, O>>,
+ mapper: (a: O, b: T) => _T,
+ ) => Promise<IEither<E, _T>>;
+}
+
+const ELeftTag = 'E.Left' as const;
+type ELeftTag = typeof ELeftTag;
+export const isLeft = <E>(o: unknown): o is Left<E> => isTagged(o, ELeftTag);
+interface Left<E> extends Tagged<ELeftTag> {
+ err: E;
+}
+
+const ERightTag = 'E.Right' as const;
+type ERightTag = typeof ERightTag;
+export const isRight = <T>(o: unknown): o is Right<T> => isTagged(o, ERightTag);
+interface Right<T> extends Tagged<ERightTag> {
+ ok: T;
+}
+
+class _Tagged implements Tagged<IEitherTag> {
+ protected constructor(public readonly _tag = IEitherTag) {}
+}
+
+export class Either<E, T> extends _Tagged implements IEither<E, T> {
+ protected constructor(private readonly self: Left<E> | Right<T>) {
+ super();
+ }
+
+ public moveRight<_T>(t: _T) {
+ return this.mapRight(() => t);
+ }
+
+ public mapBoth<_E, _T>(errBranch: Mapper<E, _E>, okBranch: Mapper<T, _T>): IEither<_E, _T> {
+ if (isLeft(this.self)) return Either.left(errBranch(this.self.err));
+ return Either.right(okBranch(this.self.ok));
+ }
+
+ public mapRight<_T>(mapper: Mapper<T, _T>): IEither<E, _T> {
+ if (isRight(this.self)) return Either.right(mapper(this.self.ok));
+ return Either.left(this.self.err);
+ }
+
+ public mapLeft<_E>(mapper: Mapper<E, _E>): IEither<_E, T> {
+ if (isLeft(this.self)) return Either.left(mapper(this.self.err));
+ return Either.right(this.self.ok);
+ }
+
+ public flatMap<_T>(mapper: Mapper<T, IEither<E, _T>>): IEither<E, _T> {
+ if (isRight(this.self)) return mapper(this.self.ok);
+ return Either.left<E, _T>(this.self.err);
+ }
+
+ public filter(mapper: Predicate<T>): IEither<E, T> {
+ if (isLeft(this.self)) return Either.left<E, T>(this.self.err);
+ return Either.fromFailable<E, T>(() => this.right().filter(mapper).get());
+ }
+
+ public async flatMapAsync<_T>(mapper: Mapper<T, Promise<IEither<E, _T>>>): Promise<IEither<E, _T>> {
+ if (isLeft(this.self)) return Promise.resolve(Either.left(this.self.err));
+ return await mapper(this.self.ok).catch((err) => Either.left(err));
+ }
+
+ public fold<_T>(leftFolder: Mapper<E, _T>, rightFolder: Mapper<T, _T>): _T {
+ if (isLeft(this.self)) return leftFolder(this.self.err);
+ return rightFolder(this.self.ok);
+ }
+
+ public left(): IOptional<E> {
+ if (isLeft(this.self)) return Optional.from(this.self.err) as IOptional<E>;
+ return Optional.none();
+ }
+
+ public right(): IOptional<T> {
+ if (isRight(this.self)) return Optional.from(this.self.ok) as IOptional<T>;
+ return Optional.none();
+ }
+
+ public joinRight<O, _T>(other: IEither<E, O>, mapper: BiMapper<O, T, _T>) {
+ return this.flatMap((t) => other.mapRight((o) => mapper(o, t)));
+ }
+
+ public joinRightAsync<O, _T>(
+ other: Supplier<Promise<IEither<E, O>>> | Promise<IEither<E, O>>,
+ mapper: BiMapper<O, T, _T>,
+ ) {
+ return this.flatMapAsync(async (t) => {
+ const o = typeof other === 'function' ? other() : other;
+ return await o.then((other) => other.mapRight((o) => mapper(o, t)));
+ });
+ }
+
+ static left<E, T>(e: E): IEither<E, T> {
+ return new Either({ err: e, _tag: ELeftTag });
+ }
+
+ static right<E, T>(t: T): IEither<E, T> {
+ return new Either({ ok: t, _tag: ERightTag });
+ }
+
+ static fromFailable<E, T>(s: Supplier<T>): IEither<E, T> {
+ try {
+ return Either.right(s());
+ } catch (e) {
+ return Either.left(e as E);
+ }
+ }
+
+ static async fromFailableAsync<E, T>(s: Supplier<Promise<T>> | Promise<T>): Promise<IEither<E, T>> {
+ return await (typeof s === 'function' ? s() : s)
+ .then((t: T) => Either.right<E, T>(t))
+ .catch((e: E) => Either.left<E, T>(e));
+ }
+}
diff --git a/lib/types/fn/index.ts b/lib/types/fn/index.ts
new file mode 100644
index 0000000..191d538
--- /dev/null
+++ b/lib/types/fn/index.ts
@@ -0,0 +1,3 @@
+export * from './callable';
+export * from './optional';
+export * from './either';
diff --git a/lib/types/fn/optional.ts b/lib/types/fn/optional.ts
new file mode 100644
index 0000000..504e496
--- /dev/null
+++ b/lib/types/fn/optional.ts
@@ -0,0 +1,93 @@
+import { type Mapper, Predicate, type Supplier, Tagged, isTagged } from '@emprespresso/pengueno';
+
+export type MaybeGiven<T> = T | undefined | null;
+
+export const IOptionalTag = 'IOptional' as const;
+export type IOptionalTag = typeof IOptionalTag;
+export const isOptional = <T>(o: unknown): o is IOptional<T> => isTagged(o, IOptionalTag);
+export class IOptionalEmptyError extends Error {}
+export interface IOptional<t, T extends NonNullable<t> = NonNullable<t>> extends Tagged<IOptionalTag>, Iterable<T> {
+ readonly move: <_T>(t: MaybeGiven<_T>) => IOptional<_T>;
+ readonly map: <_T>(mapper: Mapper<T, MaybeGiven<_T>>) => IOptional<_T>;
+ readonly filter: (mapper: Predicate<T>) => IOptional<T>;
+ readonly flatMap: <_T>(mapper: Mapper<T, MaybeGiven<IOptional<_T>>>) => IOptional<_T>;
+ readonly orSome: (supplier: Supplier<MaybeGiven<t>>) => IOptional<T>;
+ readonly get: Supplier<T>;
+ readonly present: Supplier<boolean>;
+}
+
+type OSomeTag = typeof OSomeTag;
+const OSomeTag = 'O.Some' as const;
+interface Some<T> extends Tagged<OSomeTag> {
+ value: NonNullable<T>;
+}
+
+const ONoneTag = 'O.None' as const;
+type ONoneTag = typeof ONoneTag;
+interface None extends Tagged<ONoneTag> {}
+
+const isNone = (o: unknown): o is None => isTagged(o, ONoneTag);
+const isSome = <T>(o: unknown): o is Some<T> => isTagged(o, OSomeTag);
+
+class _Tagged implements Tagged<IOptionalTag> {
+ protected constructor(public readonly _tag = IOptionalTag) {}
+}
+
+export class Optional<t, T extends NonNullable<t> = NonNullable<t>> extends _Tagged implements IOptional<T> {
+ private constructor(private readonly self: Some<T> | None) {
+ super();
+ }
+
+ public move<_T>(t: MaybeGiven<_T>): IOptional<_T> {
+ return this.map(() => t);
+ }
+
+ public orSome(supplier: Supplier<MaybeGiven<t>>): IOptional<T> {
+ if (isNone(this.self)) return Optional.from(supplier());
+ return this;
+ }
+
+ public get(): T {
+ if (isNone(this.self)) throw new IOptionalEmptyError('called get() on None optional');
+ return this.self.value;
+ }
+
+ public filter(mapper: Predicate<T>): IOptional<T> {
+ if (isNone(this.self) || !mapper(this.self.value)) return Optional.none();
+ return Optional.some(this.self.value);
+ }
+
+ public map<_T>(mapper: Mapper<T, MaybeGiven<_T>>): IOptional<_T> {
+ if (isNone(this.self)) return Optional.none();
+ return Optional.from(mapper(this.self.value)) as IOptional<_T>;
+ }
+
+ public flatMap<_T>(mapper: Mapper<T, MaybeGiven<IOptional<_T>>>): IOptional<_T> {
+ if (isNone(this.self)) return Optional.none();
+ return Optional.from(mapper(this.self.value))
+ .orSome(() => Optional.none())
+ .get();
+ }
+
+ public present() {
+ return isSome(this.self);
+ }
+
+ *[Symbol.iterator]() {
+ if (isSome(this.self)) yield this.self.value;
+ }
+
+ static some<t, T extends NonNullable<t> = NonNullable<t>>(value: T): IOptional<T> {
+ return new Optional({ value, _tag: OSomeTag });
+ }
+
+ private static readonly _none = new Optional({ _tag: ONoneTag });
+ static none<T>(): IOptional<T> {
+ return this._none as unknown as IOptional<T>;
+ }
+
+ static from<t, T extends NonNullable<t> = NonNullable<t>>(value: MaybeGiven<t>): IOptional<T> {
+ if (value === null || value === undefined) return Optional.none<T>();
+ return Optional.some(<T>value);
+ }
+}