Compare commits

...

3 Commits

Author SHA1 Message Date
e696e5d9d2 Add do notation for either 2025-05-27 16:31:13 -05:00
75368c8ef8 Add Do notation for option 2025-05-27 16:31:00 -05:00
52e00c3f38 Update to use Either correctly 2025-05-27 13:31:22 -05:00
3 changed files with 82 additions and 12 deletions

View File

@ -14,15 +14,55 @@ export const right = <A>(a: A): Either<A, never> => ({
// Operations
export const eitherMap =
<E, A, B>(f: (a: A) => B) => (fa: Either<A, E>): Either<B, E> =>
<A, B, E>(f: (a: A) => B) => (fa: Either<A, E>): Either<B, E> =>
fa._tag === 'Right' ? right(f(fa.right)) : fa
export const eitherChain =
<E, A, B>(f: (a: A) => Either<B, E>) =>
(fa: Either<A, E>): Either<B, E> =>
<A, B, E>(f: (a: A) => Either<B, E>) => (fa: Either<A, E>): Either<B, E> =>
fa._tag === 'Right' ? f(fa.right) : fa
export const fold =
<A, B, E>(onLeft: (e: E) => B, onRight: (a: A) => B) =>
(fa: Either<A, E>): B =>
fa._tag === 'Left' ? onLeft(fa.left) : onRight(fa.right)
// Do notation
export class DoEither<T extends Record<string, unknown>, E> {
constructor(private readonly either: Either<T, E>) {}
bind<K extends string, A>(
key: K,
fa: Either<A, E>,
): DoEither<T & Record<K, A>, E> {
const newEither = eitherChain<T, T & Record<K, A>, E>((scope: T) =>
eitherMap<A, T & Record<K, A>, E>((a: A) => ({...scope, [key]: a}))(fa)
)(this.either)
return new DoEither(newEither)
}
return<B>(f: (scope: T) => B): Either<B, E> {
return eitherMap<T, B, E>(f)(this.either)
}
done(): Either<T, E> {
return this.either
}
do(fa: Either<unknown, E>): DoEither<T, E> {
const newEither = eitherChain<T, T, E>((scope: T) =>
eitherMap<unknown, T, E>(() => scope)(fa)
)(this.either)
return new DoEither(newEither)
}
doL(f: (scope: T) => Either<unknown, E>): DoEither<T, E> {
const newEither = eitherChain<T, T, E>((scope: T) =>
eitherMap<unknown, T, E>(() => scope)(f(scope))
)(this.either)
return new DoEither(newEither)
}
static start<E>(): DoEither<{}, E> {
return new DoEither(right({}))
}
}

View File

@ -10,9 +10,8 @@ export const some = <A>(value: A): Option<A> => ({
})
// Operations
export const map =
<A, B>(f: (a: A) => B) => (fa: Option<A>): Option<B> =>
fa._tag === 'Some' ? some(f(fa.value)) : none
export const map = <A, B>(f: (a: A) => B) => (fa: Option<A>): Option<B> =>
fa._tag === 'Some' ? some(f(fa.value)) : none
export const chain =
<A, B>(f: (a: A) => Option<B>) => (fa: Option<A>): Option<B> =>
@ -20,3 +19,37 @@ export const chain =
export const getOrElse = <A>(defaultValue: A) => (fa: Option<A>): A =>
fa._tag === 'Some' ? fa.value : defaultValue
export class DoOption<T extends Record<string, unknown>> {
constructor(private readonly option: Option<T>) {}
bind<K extends string, A>(key: K, fa: Option<A>): DoOption<T & Record<K, A>> {
const newOption = chain((scope: T) =>
map((a: A) => ({...scope, [key]: a}))(fa)
)(this.option)
return new DoOption(newOption as Option<T & Record<K, A>>)
}
return<B>(f: (scope: T) => B): Option<B> {
return map(f)(this.option)
}
done(): Option<T> {
return this.option
}
do(fa: Option<unknown>): DoOption<T> {
const newOption = chain((scope: T) => map(() => scope)(fa))(this.option)
return new DoOption(newOption)
}
doL(f: (scope: T) => Option<unknown>): DoOption<T> {
const newOption = chain((scope: T) => map(() => scope)(f(scope)))(
this.option,
)
return new DoOption(newOption)
}
static start(): DoOption<{}> {
return new DoOption(some({}))
}
}

View File

@ -2,14 +2,11 @@ import { type Either, left, right } from './either'
import { none, type Option, some } from './option'
// Pipe function for composition
export const pipe = <A, B, C>(
a: A,
ab: (a: A) => B,
bc: (b: B) => C,
): C => bc(ab(a))
export const pipe = <A, B, C>(a: A, ab: (a: A) => B, bc: (b: B) => C): C =>
bc(ab(a))
// TryCatch helper using Either
export const tryCatch = <A>(f: () => A): Either<Error, A> => {
export const tryCatch = <A>(f: () => A): Either<A, Error> => {
try {
return right(f())
} catch (e) {