From 9970036d203ba2d0a46b35ba6fad21d49441cdd4 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sun, 27 Jul 2025 17:03:10 -0700 Subject: hai --- lib/types/collections/cons.ts | 108 +++++++++++++++++++++++++++++++ lib/types/collections/index.ts | 1 + lib/types/fn/callable.ts | 21 ++++++ lib/types/fn/either.ts | 143 +++++++++++++++++++++++++++++++++++++++++ lib/types/fn/index.ts | 3 + lib/types/fn/optional.ts | 93 +++++++++++++++++++++++++++ lib/types/index.ts | 5 ++ lib/types/misc.ts | 3 + lib/types/object.ts | 1 + lib/types/tagged.ts | 8 +++ 10 files changed, 386 insertions(+) create mode 100644 lib/types/collections/cons.ts create mode 100644 lib/types/collections/index.ts create mode 100644 lib/types/fn/callable.ts create mode 100644 lib/types/fn/either.ts create mode 100644 lib/types/fn/index.ts create mode 100644 lib/types/fn/optional.ts create mode 100644 lib/types/index.ts create mode 100644 lib/types/misc.ts create mode 100644 lib/types/object.ts create mode 100644 lib/types/tagged.ts (limited to 'lib/types') diff --git a/lib/types/collections/cons.ts b/lib/types/collections/cons.ts new file mode 100644 index 0000000..b671d71 --- /dev/null +++ b/lib/types/collections/cons.ts @@ -0,0 +1,108 @@ +import { IOptional, Mapper, Optional, Supplier } from '@emprespresso/pengueno'; + +export interface ICons extends Iterable { + readonly value: T; + readonly next: IOptional>; + + readonly replace: Mapper>; + readonly before: Mapper>, ICons>; +} + +export class Cons implements ICons { + constructor( + public readonly value: T, + public readonly next: IOptional> = Optional.none(), + ) {} + + public before(head: IOptional>): ICons { + return new Cons(this.value, head); + } + + public replace(_value: T): ICons { + return new Cons(_value, this.next); + } + + *[Symbol.iterator]() { + for (let cur = Optional.some>(this); cur.present(); cur = cur.flatMap((cur) => cur.next)) { + yield cur.get().value; + } + } + + static addOnto(items: Iterable, tail: IOptional>): IOptional> { + return Array.from(items) + .reverse() + .reduce((cons, value) => Optional.from>(new Cons(value, cons)), tail); + } + + static from(items: Iterable): IOptional> { + return Cons.addOnto(items, Optional.none()); + } +} + +export interface IZipper extends Iterable { + readonly read: Supplier>; + readonly next: Supplier>>; + readonly previous: Supplier>>; + + readonly prependChunk: Mapper, IZipper>; + readonly prepend: Mapper>; + readonly remove: Supplier>; + readonly replace: Mapper>; +} + +export class ListZipper implements IZipper { + private constructor( + private readonly reversedPathToHead: IOptional>, + private readonly currentHead: IOptional>, + ) {} + + public read(): IOptional { + return this.currentHead.map(({ value }) => value); + } + + public next(): IOptional> { + return this.currentHead.map>( + (head) => new ListZipper(Optional.some(head.before(this.reversedPathToHead)), head.next), + ); + } + + public previous(): IOptional> { + return this.reversedPathToHead.map>( + (lastVisited) => new ListZipper(lastVisited.next, Optional.some(lastVisited.before(this.currentHead))), + ); + } + + public prependChunk(values: Iterable): IZipper { + return new ListZipper(Cons.addOnto(Array.from(values).reverse(), this.reversedPathToHead), this.currentHead); + } + + public prepend(value: T): IZipper { + return this.prependChunk([value]); + } + + public remove(): IZipper { + const newHead = this.currentHead.flatMap((right) => right.next); + return new ListZipper(this.reversedPathToHead, newHead); + } + + public replace(value: T): IZipper { + const newHead = this.currentHead.map((right) => right.replace(value)); + return new ListZipper(this.reversedPathToHead, newHead); + } + + *[Symbol.iterator]() { + let head: ListZipper = this; + for (let prev = head.previous(); prev.present(); prev = prev.flatMap((p) => p.previous())) { + head = >prev.get(); + } + if (head.currentHead.present()) yield* head.currentHead.get(); + } + + public collection() { + return Array.from(this); + } + + static from(iterable: Iterable): ListZipper { + return new ListZipper(Optional.none(), Cons.from(iterable)); + } +} diff --git a/lib/types/collections/index.ts b/lib/types/collections/index.ts new file mode 100644 index 0000000..8a12ad8 --- /dev/null +++ b/lib/types/collections/index.ts @@ -0,0 +1 @@ +export * from './cons'; 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 { + (...args: Array): T; +} + +export interface Supplier extends Callable { + (): T; +} + +export interface Mapper extends Callable { + (t: T): U; +} + +export interface Predicate extends Mapper {} + +export interface BiMapper extends Callable { + (t: T, u: U): R; +} + +export interface SideEffect extends Mapper {} + +export interface BiSideEffect extends BiMapper {} 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 = (o: unknown): o is IEither => isTagged(o, IEitherTag); +export interface IEither extends Tagged { + readonly left: Supplier>; + readonly right: Supplier>; + + readonly mapRight: <_T>(mapper: Mapper) => IEither; + readonly filter: (mapper: Predicate) => IEither; + readonly mapLeft: <_E>(mapper: Mapper) => IEither<_E, T>; + readonly mapBoth: <_E, _T>(errBranch: Mapper, okBranch: Mapper) => IEither<_E, _T>; + + readonly flatMap: <_T>(mapper: Mapper>) => IEither; + readonly flatMapAsync: <_T>(mapper: Mapper>>) => Promise>; + + readonly moveRight: <_T>(t: _T) => IEither; + readonly fold: <_T>(leftFolder: Mapper, rightFolder: Mapper) => _T; + readonly joinRight: (other: IEither, mapper: (a: O, b: T) => _T) => IEither; + readonly joinRightAsync: ( + other: (() => Promise>) | Promise>, + mapper: (a: O, b: T) => _T, + ) => Promise>; +} + +const ELeftTag = 'E.Left' as const; +type ELeftTag = typeof ELeftTag; +export const isLeft = (o: unknown): o is Left => isTagged(o, ELeftTag); +interface Left extends Tagged { + err: E; +} + +const ERightTag = 'E.Right' as const; +type ERightTag = typeof ERightTag; +export const isRight = (o: unknown): o is Right => isTagged(o, ERightTag); +interface Right extends Tagged { + ok: T; +} + +class _Tagged implements Tagged { + protected constructor(public readonly _tag = IEitherTag) {} +} + +export class Either extends _Tagged implements IEither { + protected constructor(private readonly self: Left | Right) { + super(); + } + + public moveRight<_T>(t: _T) { + return this.mapRight(() => t); + } + + public mapBoth<_E, _T>(errBranch: Mapper, okBranch: Mapper): 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): IEither { + if (isRight(this.self)) return Either.right(mapper(this.self.ok)); + return Either.left(this.self.err); + } + + public mapLeft<_E>(mapper: Mapper): 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>): IEither { + if (isRight(this.self)) return mapper(this.self.ok); + return Either.left(this.self.err); + } + + public filter(mapper: Predicate): IEither { + if (isLeft(this.self)) return Either.left(this.self.err); + return Either.fromFailable(() => this.right().filter(mapper).get()); + } + + public async flatMapAsync<_T>(mapper: Mapper>>): Promise> { + 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, rightFolder: Mapper): _T { + if (isLeft(this.self)) return leftFolder(this.self.err); + return rightFolder(this.self.ok); + } + + public left(): IOptional { + if (isLeft(this.self)) return Optional.from(this.self.err) as IOptional; + return Optional.none(); + } + + public right(): IOptional { + if (isRight(this.self)) return Optional.from(this.self.ok) as IOptional; + return Optional.none(); + } + + public joinRight(other: IEither, mapper: BiMapper) { + return this.flatMap((t) => other.mapRight((o) => mapper(o, t))); + } + + public joinRightAsync( + other: Supplier>> | Promise>, + mapper: BiMapper, + ) { + 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: E): IEither { + return new Either({ err: e, _tag: ELeftTag }); + } + + static right(t: T): IEither { + return new Either({ ok: t, _tag: ERightTag }); + } + + static fromFailable(s: Supplier): IEither { + try { + return Either.right(s()); + } catch (e) { + return Either.left(e as E); + } + } + + static async fromFailableAsync(s: Supplier> | Promise): Promise> { + return await (typeof s === 'function' ? s() : s) + .then((t: T) => Either.right(t)) + .catch((e: E) => Either.left(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 | undefined | null; + +export const IOptionalTag = 'IOptional' as const; +export type IOptionalTag = typeof IOptionalTag; +export const isOptional = (o: unknown): o is IOptional => isTagged(o, IOptionalTag); +export class IOptionalEmptyError extends Error {} +export interface IOptional = NonNullable> extends Tagged, Iterable { + readonly move: <_T>(t: MaybeGiven<_T>) => IOptional<_T>; + readonly map: <_T>(mapper: Mapper>) => IOptional<_T>; + readonly filter: (mapper: Predicate) => IOptional; + readonly flatMap: <_T>(mapper: Mapper>>) => IOptional<_T>; + readonly orSome: (supplier: Supplier>) => IOptional; + readonly get: Supplier; + readonly present: Supplier; +} + +type OSomeTag = typeof OSomeTag; +const OSomeTag = 'O.Some' as const; +interface Some extends Tagged { + value: NonNullable; +} + +const ONoneTag = 'O.None' as const; +type ONoneTag = typeof ONoneTag; +interface None extends Tagged {} + +const isNone = (o: unknown): o is None => isTagged(o, ONoneTag); +const isSome = (o: unknown): o is Some => isTagged(o, OSomeTag); + +class _Tagged implements Tagged { + protected constructor(public readonly _tag = IOptionalTag) {} +} + +export class Optional = NonNullable> extends _Tagged implements IOptional { + private constructor(private readonly self: Some | None) { + super(); + } + + public move<_T>(t: MaybeGiven<_T>): IOptional<_T> { + return this.map(() => t); + } + + public orSome(supplier: Supplier>): IOptional { + 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): IOptional { + if (isNone(this.self) || !mapper(this.self.value)) return Optional.none(); + return Optional.some(this.self.value); + } + + public map<_T>(mapper: Mapper>): IOptional<_T> { + if (isNone(this.self)) return Optional.none(); + return Optional.from(mapper(this.self.value)) as IOptional<_T>; + } + + public flatMap<_T>(mapper: Mapper>>): 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 = NonNullable>(value: T): IOptional { + return new Optional({ value, _tag: OSomeTag }); + } + + private static readonly _none = new Optional({ _tag: ONoneTag }); + static none(): IOptional { + return this._none as unknown as IOptional; + } + + static from = NonNullable>(value: MaybeGiven): IOptional { + if (value === null || value === undefined) return Optional.none(); + return Optional.some(value); + } +} diff --git a/lib/types/index.ts b/lib/types/index.ts new file mode 100644 index 0000000..5c4e4d2 --- /dev/null +++ b/lib/types/index.ts @@ -0,0 +1,5 @@ +export * from './misc'; +export * from './object'; +export * from './tagged'; +export * from './fn'; +export * from './collections'; diff --git a/lib/types/misc.ts b/lib/types/misc.ts new file mode 100644 index 0000000..77833c4 --- /dev/null +++ b/lib/types/misc.ts @@ -0,0 +1,3 @@ +export type ObjectFromList, V = string> = { + [K in T extends ReadonlyArray ? U : never]: V; +}; diff --git a/lib/types/object.ts b/lib/types/object.ts new file mode 100644 index 0000000..fe97999 --- /dev/null +++ b/lib/types/object.ts @@ -0,0 +1 @@ +export const isObject = (o: unknown): o is object => typeof o === 'object' && !Array.isArray(o) && !!o; diff --git a/lib/types/tagged.ts b/lib/types/tagged.ts new file mode 100644 index 0000000..31607b0 --- /dev/null +++ b/lib/types/tagged.ts @@ -0,0 +1,8 @@ +import { isObject } from '.'; + +export interface Tagged { + _tag: TTag; +} + +export const isTagged = (o: unknown, tag: TTag): o is Tagged => + !!(isObject(o) && '_tag' in o && o._tag === tag); -- cgit v1.2.3-70-g09d2