summaryrefslogtreecommitdiff
path: root/u
diff options
context:
space:
mode:
authorElizabeth <me@liz.coffee>2025-06-02 16:52:52 -0700
committerElizabeth <me@liz.coffee>2025-06-02 16:52:52 -0700
commit98f5c21aa65bbbca01a186a754249335b4afef57 (patch)
tree0fc8e01a73f0a3be4534c11724ad2ff634b4fd2f /u
parent373d9ec700c0097a22cf665a8e33cf48998d1dc2 (diff)
downloadci-98f5c21aa65bbbca01a186a754249335b4afef57.tar.gz
ci-98f5c21aa65bbbca01a186a754249335b4afef57.zip
fixup the Either monad a bit for type safetyp
Diffstat (limited to 'u')
-rw-r--r--u/fn/either.ts183
-rw-r--r--u/process/argv.ts13
-rw-r--r--u/server/activity/health.ts12
-rw-r--r--u/server/response.ts4
4 files changed, 117 insertions, 95 deletions
diff --git a/u/fn/either.ts b/u/fn/either.ts
index b228af2..124557c 100644
--- a/u/fn/either.ts
+++ b/u/fn/either.ts
@@ -1,97 +1,118 @@
-import type { BiMapper, Mapper, Supplier } from "@emprespresso/pengueno";
-import { isObject } from "../leftpadesque/mod.ts";
+import { type Mapper, type Supplier, isObject } from "@emprespresso/pengueno";
type IEitherTag = "IEither";
const iEitherTag: IEitherTag = "IEither";
+export interface _Either<LeftT, RightT, T> {
+ readonly isLeft: LeftT;
+ readonly isRight: RightT;
+ readonly value: T;
+}
+export type Left<E> = _Either<true, false, E>;
+export type Right<T> = _Either<false, true, T>;
+
export interface IEither<E, T> {
- readonly _tag: IEitherTag;
- mapBoth: <Ee, Tt>(
- errBranch: Mapper<E, Ee>,
- okBranch: Mapper<T, Tt>,
- ) => IEither<Ee, Tt>;
- fold: <Tt>(folder: (err: E | undefined, val: T | undefined) => Tt) => Tt; //BiMapper<E | undefined, T | undefined, Tt>) => Tt;;
- moveRight: <Tt>(t: Tt) => IEither<E, Tt>;
- mapRight: <Tt>(mapper: Mapper<T, Tt>) => IEither<E, Tt>;
- mapLeft: <Ee>(mapper: Mapper<E, Ee>) => IEither<Ee, T>;
- flatMap: <Tt>(mapper: Mapper<T, IEither<E, Tt>>) => IEither<E, Tt>;
- flatMapAsync: <Tt>(
- mapper: Mapper<T, Promise<IEither<E, Tt>>>,
- ) => Promise<IEither<E, Tt>>;
+ readonly _tag: IEitherTag;
+
+ mapBoth: <_E, _T>(
+ errBranch: Mapper<E, _E>,
+ okBranch: Mapper<T, _T>,
+ ) => IEither<_E, _T>;
+ fold: <_T>(folder: Mapper<Left<E> | Right<T>, _T>) => _T;
+ moveRight: <_T>(t: _T) => IEither<E, _T>;
+ mapRight: <_T>(mapper: Mapper<T, _T>) => IEither<E, _T>;
+ mapLeft: <_E>(mapper: Mapper<E, _E>) => IEither<_E, T>;
+ flatMap: <_T>(mapper: Mapper<T, IEither<E, _T>>) => IEither<E, _T>;
+ flatMapAsync: <_T>(
+ mapper: Mapper<T, Promise<IEither<E, _T>>>,
+ ) => Promise<IEither<E, _T>>;
}
export class Either<E, T> implements IEither<E, T> {
- private constructor(
- private readonly err?: E,
- private readonly ok?: T,
- public readonly _tag: IEitherTag = iEitherTag,
- ) {}
-
- public moveRight<Tt>(t: Tt) {
- return this.mapRight(() => t);
- }
-
- public fold<R>(folder: BiMapper<E | undefined, T | undefined, R>): R {
- return folder(this.err ?? undefined, this.ok ?? undefined);
- }
-
- public mapBoth<Ee, Tt>(
- errBranch: Mapper<E, Ee>,
- okBranch: Mapper<T, Tt>,
- ): Either<Ee, Tt> {
- if (this.err !== undefined) return Either.left(errBranch(this.err));
- return Either.right(okBranch(this.ok!));
- }
-
- public flatMap<Tt>(mapper: Mapper<T, Either<E, Tt>>): Either<E, Tt> {
- if (this.ok !== undefined) return mapper(this.ok);
- return Either.left<E, Tt>(this.err!);
- }
-
- public mapRight<Tt>(mapper: Mapper<T, Tt>): IEither<E, Tt> {
- if (this.ok !== undefined) return Either.right<E, Tt>(mapper(this.ok));
- return Either.left<E, Tt>(this.err!);
- }
-
- public mapLeft<Ee>(mapper: Mapper<E, Ee>): IEither<Ee, T> {
- if (this.err !== undefined) return Either.left<Ee, T>(mapper(this.err));
- return Either.right<Ee, T>(this.ok!);
- }
-
- public async flatMapAsync<Tt>(
- mapper: Mapper<T, Promise<IEither<E, Tt>>>,
- ): Promise<IEither<E, Tt>> {
- if (this.err !== undefined) {
- return Promise.resolve(Either.left<E, Tt>(this.err));
+ private readonly self: Left<E> | Right<T>;
+
+ private constructor(
+ err?: E,
+ ok?: T,
+ public readonly _tag: IEitherTag = iEitherTag,
+ ) {
+ this.self = <Left<E> | Right<T>>{
+ isLeft: typeof err !== "undefined",
+ isRight: typeof ok !== "undefined",
+ value: typeof err !== "undefined" ? err : ok!,
+ };
+ }
+
+ public moveRight<_T>(t: _T) {
+ return this.mapRight(() => t);
+ }
+
+ public fold<_T>(folder: Mapper<Left<E> | Right<T>, _T>): _T {
+ return folder(this.self);
+ }
+
+ public mapBoth<_E, _T>(
+ errBranch: Mapper<E, _E>,
+ okBranch: Mapper<T, _T>,
+ ): IEither<_E, _T> {
+ if (this.self.isLeft) return Either.left(errBranch(this.self.value));
+ return Either.right(okBranch(this.self.value));
+ }
+
+ public flatMap<_T>(mapper: Mapper<T, IEither<E, _T>>): IEither<E, _T> {
+ if (this.self.isRight) return mapper(this.self.value);
+ return Either.left<E, _T>(this.self.value);
+ }
+
+ public mapRight<_T>(mapper: Mapper<T, _T>): IEither<E, _T> {
+ if (this.self.isRight)
+ return Either.right<E, _T>(mapper(this.self.value));
+ return Either.left<E, _T>(this.self.value);
}
- return await mapper(this.ok!).catch((err) => Either.left<E, Tt>(err as E));
- }
-
- static left<E, T>(e: E) {
- return new Either<E, T>(e);
- }
-
- static right<E, T>(t: T) {
- return new Either<E, T>(undefined, t);
- }
-
- static fromFailable<E, T>(s: Supplier<T>) {
- try {
- return Either.right<E, T>(s());
- } catch (e) {
- return Either.left<E, T>(e as E);
+
+ public mapLeft<_E>(mapper: Mapper<E, _E>): IEither<_E, T> {
+ if (this.self.isLeft)
+ return Either.left<_E, T>(mapper(this.self.value));
+ return Either.right<_E, T>(this.self.value);
+ }
+
+ public async flatMapAsync<_T>(
+ mapper: Mapper<T, Promise<IEither<E, _T>>>,
+ ): Promise<IEither<E, _T>> {
+ if (this.self.isLeft) {
+ return Promise.resolve(Either.left<E, _T>(this.self.value));
+ }
+ return await mapper(this.self.value).catch((err) =>
+ Either.left<E, _T>(err),
+ );
+ }
+
+ static left<E, T>(e: E): IEither<E, T> {
+ return new Either<E, T>(e, undefined);
+ }
+ static right<E, T>(t: T): IEither<E, T> {
+ return new Either<E, T>(undefined, t);
+ }
+
+ static fromFailable<E, T>(s: Supplier<T>): IEither<E, T> {
+ try {
+ return Either.right<E, T>(s());
+ } catch (e) {
+ return Either.left<E, T>(e as E);
+ }
}
- }
- static async fromFailableAsync<E, T>(s: Promise<T>) {
- try {
- return Either.right<E, T>(await s);
- } catch (e) {
- return Either.left<E, T>(e as E);
+ static async fromFailableAsync<E, T>(
+ s: Promise<T>,
+ ): Promise<IEither<E, T>> {
+ try {
+ return Either.right<E, T>(await s);
+ } catch (e) {
+ return Either.left<E, T>(e as E);
+ }
}
- }
}
export const isEither = <E, T>(o: unknown): o is IEither<E, T> => {
- return isObject(o) && "_tag" in o && o._tag === "IEither";
+ return isObject(o) && "_tag" in o && o._tag === "IEither";
};
diff --git a/u/process/argv.ts b/u/process/argv.ts
index 657c9a7..7190531 100644
--- a/u/process/argv.ts
+++ b/u/process/argv.ts
@@ -37,14 +37,15 @@ export const argv = <K extends string, V extends string>(
.map((arg) => [arg, getArg(arg, argv)] as [K, IEither<Error, V>])
.map(([arg, specified]): [K, IEither<Error, V>] => [
arg,
- specified.fold((e, val) => {
- const hasDefaultVal = e && defaultArgs && arg in defaultArgs;
+ specified.fold(({ isLeft, isRight, value}): IEither<Error, V> => {
+ if (isRight) {
+ return Either.right(value);
+ }
+ const hasDefaultVal = isLeft && defaultArgs && arg in defaultArgs;
if (hasDefaultVal) {
- return Either.right<Error, V>(defaultArgs[arg]!);
- } else if (!val || e) {
- return Either.left<Error, V>(e ?? new Error("unknown"));
+ return Either.right(defaultArgs[arg]!);
}
- return Either.right<Error, V>(val);
+ return Either.left(value);
}),
])
.reduce(
diff --git a/u/server/activity/health.ts b/u/server/activity/health.ts
index 83be399..b9dedf9 100644
--- a/u/server/activity/health.ts
+++ b/u/server/activity/health.ts
@@ -38,10 +38,10 @@ export class HealthCheckActivityImpl implements IHealthCheckActivity {
.flatMap((r) => r.move(HealthCheckInput.CHECK).map(this.check))
.peek(
TraceUtil.promiseify((h) =>
- h.get().fold((err) => {
- if (err) {
+ h.get().fold(({ isLeft, value }) => {
+ if (isLeft) {
h.trace.trace(healthCheckMetric.failure);
- h.trace.addTrace(LogLevel.ERROR).trace(`${err}`);
+ h.trace.addTrace(LogLevel.ERROR).trace(`${value}`);
return;
}
h.trace.trace(healthCheckMetric.success);
@@ -57,9 +57,9 @@ export class HealthCheckActivityImpl implements IHealthCheckActivity {
() => "think im healthy!! (✿˘◡˘) ready to do work~",
)
.fold(
- (errMsg, okMsg) =>
- new JsonResponse(req, errMsg ?? okMsg, {
- status: errMsg ? 500 : 200,
+ ({ isLeft, value: message }) =>
+ new JsonResponse(req, message, {
+ status: isLeft ? 500 : 200,
}),
),
),
diff --git a/u/server/response.ts b/u/server/response.ts
index 9022fed..4531157 100644
--- a/u/server/response.ts
+++ b/u/server/response.ts
@@ -67,7 +67,7 @@ export class JsonResponse extends PenguenoResponse {
super(
req,
JSON.stringify(
- e.fold((err, ok) => (err ? { error: err! } : { ok: ok! })),
+ e.fold(({ isLeft, value }) => (isLeft ? { error: value } : { ok: value })),
),
optsWithJsonContentType,
);
@@ -76,7 +76,7 @@ export class JsonResponse extends PenguenoResponse {
super(
req,
JSON.stringify(
- Math.floor(opts.status / 100) < 4 ? { ok: e } : { error: e },
+ Math.floor(opts.status / 100) > 4 ? { error: e } : { ok: e },
),
optsWithJsonContentType,
);