diff options
author | Elizabeth Hunt <me@liz.coffee> | 2025-06-29 17:31:30 -0700 |
---|---|---|
committer | Elizabeth Hunt <me@liz.coffee> | 2025-06-29 17:31:30 -0700 |
commit | 58be1809c46cbe517a18d86d0af52179dcc5cbf6 (patch) | |
tree | 9ccc678b3fd48c1a52fe501600dd2c2051740a55 /u/types | |
parent | d4791f3d357634daf506fb8f91cc5332a794c421 (diff) | |
download | ci-58be1809c46cbe517a18d86d0af52179dcc5cbf6.tar.gz ci-58be1809c46cbe517a18d86d0af52179dcc5cbf6.zip |
Move to nodejs and also lots of significant refactoring that should've been broken up but idgaf
Diffstat (limited to 'u/types')
-rw-r--r-- | u/types/collections/cons.ts | 40 | ||||
-rw-r--r-- | u/types/collections/index.ts | 2 | ||||
-rw-r--r-- | u/types/collections/list_zipper.ts | 70 | ||||
-rw-r--r-- | u/types/fn/callable.ts | 19 | ||||
-rw-r--r-- | u/types/fn/either.ts | 106 | ||||
-rw-r--r-- | u/types/fn/index.ts | 3 | ||||
-rw-r--r-- | u/types/fn/optional.ts | 93 | ||||
-rw-r--r-- | u/types/index.ts | 5 | ||||
-rw-r--r-- | u/types/object.ts | 1 | ||||
-rw-r--r-- | u/types/tagged.ts | 8 |
10 files changed, 347 insertions, 0 deletions
diff --git a/u/types/collections/cons.ts b/u/types/collections/cons.ts new file mode 100644 index 0000000..05dbe7c --- /dev/null +++ b/u/types/collections/cons.ts @@ -0,0 +1,40 @@ +import { IOptional, Mapper, Optional } from '@emprespresso/pengueno'; + +export interface ICons<T> extends Iterable<T> { + readonly value: T; + readonly next: IOptional<ICons<T>>; + + readonly replace: Mapper<T, ICons<T>>; + readonly before: Mapper<IOptional<ICons<T>>, ICons<T>>; +} + +export class Cons<T> implements ICons<T> { + constructor( + public readonly value: T, + public readonly next: IOptional<ICons<T>> = Optional.none(), + ) {} + + public before(head: IOptional<ICons<T>>): ICons<T> { + return new Cons<T>(this.value, head); + } + + public replace(_value: T): ICons<T> { + return new Cons<T>(_value, this.next); + } + + *[Symbol.iterator]() { + for (let cur = Optional.some<ICons<T>>(this); cur.present(); cur = cur.flatMap((cur) => cur.next)) { + yield cur.get().value; + } + } + + static addOnto<T>(items: Iterable<T>, tail: IOptional<ICons<T>>): IOptional<ICons<T>> { + return Array.from(items) + .reverse() + .reduce((cons, value) => Optional.from<ICons<T>>(new Cons<T>(value, cons)), tail); + } + + static from<T>(items: Iterable<T>): IOptional<ICons<T>> { + return Cons.addOnto(items, Optional.none()); + } +} diff --git a/u/types/collections/index.ts b/u/types/collections/index.ts new file mode 100644 index 0000000..69e5d0b --- /dev/null +++ b/u/types/collections/index.ts @@ -0,0 +1,2 @@ +export * from './cons.js'; +export * from './list_zipper.js'; diff --git a/u/types/collections/list_zipper.ts b/u/types/collections/list_zipper.ts new file mode 100644 index 0000000..3df15b5 --- /dev/null +++ b/u/types/collections/list_zipper.ts @@ -0,0 +1,70 @@ +import { Cons, ICons } from './cons.js'; +import { IOptional, Mapper, Optional, Supplier } from '@emprespresso/pengueno'; + +export interface IZipper<T> extends Iterable<T> { + readonly read: Supplier<IOptional<T>>; + readonly next: Supplier<IOptional<IZipper<T>>>; + readonly previous: Supplier<IOptional<IZipper<T>>>; + + readonly prependChunk: Mapper<Iterable<T>, IZipper<T>>; + readonly prepend: Mapper<T, IZipper<T>>; + readonly remove: Supplier<IZipper<T>>; + readonly replace: Mapper<T, IZipper<T>>; +} + +export class ListZipper<T> implements IZipper<T> { + private constructor( + private readonly reversedPathToHead: IOptional<ICons<T>>, + private readonly currentHead: IOptional<ICons<T>>, + ) {} + + public read(): IOptional<T> { + return this.currentHead.map(({ value }) => value); + } + + public next(): IOptional<IZipper<T>> { + return this.currentHead.map<IZipper<T>>( + (head) => new ListZipper<T>(Optional.some(head.before(this.reversedPathToHead)), head.next), + ); + } + + public previous(): IOptional<IZipper<T>> { + return this.reversedPathToHead.map<IZipper<T>>( + (lastVisited) => new ListZipper<T>(lastVisited.next, Optional.some(lastVisited.before(this.currentHead))), + ); + } + + public prependChunk(values: Iterable<T>): IZipper<T> { + return new ListZipper<T>(Cons.addOnto(Array.from(values).reverse(), this.reversedPathToHead), this.currentHead); + } + + public prepend(value: T): IZipper<T> { + return this.prependChunk([value]); + } + + public remove(): IZipper<T> { + const newHead = this.currentHead.flatMap((right) => right.next); + return new ListZipper<T>(this.reversedPathToHead, newHead); + } + + public replace(value: T): IZipper<T> { + const newHead = this.currentHead.map((right) => right.replace(value)); + return new ListZipper<T>(this.reversedPathToHead, newHead); + } + + *[Symbol.iterator]() { + let head: ListZipper<T> = this; + for (let prev = head.previous(); prev.present(); prev = prev.flatMap((p) => p.previous())) { + head = <ListZipper<T>>prev.get(); + } + if (head.currentHead.present()) yield* head.currentHead.get(); + } + + public collection() { + return Array.from(this); + } + + static from<T>(iterable: Iterable<T>): ListZipper<T> { + return new ListZipper(Optional.none(), Cons.from(iterable)); + } +} diff --git a/u/types/fn/callable.ts b/u/types/fn/callable.ts new file mode 100644 index 0000000..51756d7 --- /dev/null +++ b/u/types/fn/callable.ts @@ -0,0 +1,19 @@ +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 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/u/types/fn/either.ts b/u/types/fn/either.ts new file mode 100644 index 0000000..6140ada --- /dev/null +++ b/u/types/fn/either.ts @@ -0,0 +1,106 @@ +import { IOptional, type Mapper, Optional, 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 mapBoth: <_E, _T>(errBranch: Mapper<E, _E>, okBranch: Mapper<T, _T>) => IEither<_E, _T>; + readonly fold: <_T>(leftFolder: Mapper<E, _T>, rightFolder: Mapper<T, _T>) => _T; + readonly left: Supplier<IOptional<E>>; + readonly right: Supplier<IOptional<T>>; + readonly moveRight: <_T>(t: _T) => IEither<E, _T>; + readonly mapRight: <_T>(mapper: Mapper<T, _T>) => IEither<E, _T>; + readonly mapLeft: <_E>(mapper: Mapper<E, _E>) => 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>>; +} + +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 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(); + } + + 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/u/types/fn/index.ts b/u/types/fn/index.ts new file mode 100644 index 0000000..780c86c --- /dev/null +++ b/u/types/fn/index.ts @@ -0,0 +1,3 @@ +export * from './callable.js'; +export * from './either.js'; +export * from './optional.js'; diff --git a/u/types/fn/optional.ts b/u/types/fn/optional.ts new file mode 100644 index 0000000..3396a45 --- /dev/null +++ b/u/types/fn/optional.ts @@ -0,0 +1,93 @@ +import { type Mapper, 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: Mapper<T, boolean>) => 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('empty value'); + return this.self.value; + } + + public filter(mapper: Mapper<T, boolean>): 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); + } +} diff --git a/u/types/index.ts b/u/types/index.ts new file mode 100644 index 0000000..c68cedc --- /dev/null +++ b/u/types/index.ts @@ -0,0 +1,5 @@ +export * from './object.js'; +export * from './tagged.js'; + +export * from './fn/index.js'; +export * from './collections/index.js'; diff --git a/u/types/object.ts b/u/types/object.ts new file mode 100644 index 0000000..fe97999 --- /dev/null +++ b/u/types/object.ts @@ -0,0 +1 @@ +export const isObject = (o: unknown): o is object => typeof o === 'object' && !Array.isArray(o) && !!o; diff --git a/u/types/tagged.ts b/u/types/tagged.ts new file mode 100644 index 0000000..351e4c9 --- /dev/null +++ b/u/types/tagged.ts @@ -0,0 +1,8 @@ +import { isObject } from './index.js'; + +export interface Tagged<TTag> { + _tag: TTag; +} + +export const isTagged = <TTag>(o: unknown, tag: TTag): o is Tagged<TTag> => + !!(isObject(o) && '_tag' in o && o._tag === tag); |