Skip to content

Instantly share code, notes, and snippets.

@lupuszr
Last active October 15, 2021 06:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lupuszr/f73baa7ef559f77e6e847ba75ed97182 to your computer and use it in GitHub Desktop.
Save lupuszr/f73baa7ef559f77e6e847ba75ed97182 to your computer and use it in GitHub Desktop.
// Proper try data structure implementation in typescript
// pass as thunk
type $<A> = () => A
function $<A>(a: A): $<A>{
return () => a
}
// make function lazy
function $fn<A extends (...args: any) => any>(a: A): ((...p: Parameters<A>) => $<ReturnType<A>>) {
return (...p: [Parameters<A>]) => () => {
return a(...p)
}
}
abstract class Try<A> {
abstract map<B>(this: Try<A>, fn: (a: A) => B): Try<B>
abstract chain<B>(this: Try<A>, fn: (a: A) => Try<B>): Try<B>;
abstract orElseTry(this: Try<A>, fallback: Try<A>): Try<A>;
abstract filterTry(this: Try<A>, fn: (a: A) => Boolean): Try<A>;
abstract ap<B>(this: Try<A>, vT: Try<Fn<A, B>>): Try<B>;
apply<B, C, X>(this: Try<A extends Fn<C, B> ? A : never>, vT: Try<C>): Try<B> {
return vT.chain(v => {
return this.map(fn => fn(v))
})
}
static liftFn<A, B>(fn: Fn<A, B>): Try<Fn<A, B>> {
return mkTry($(fn))
}
static lift<A, B>(val: A): Try<A> {
return mkTry($(val))
}
}
function mkTry<A>(value: $<A>): Try<A> {
try {
const a = value();
return Success.of(a)
} catch (error) {
return Failure.of(error as Error)
}
}
class Success<A> extends Try<A> {
value: A
__tag: "Success" = "Success";
constructor(a: A) {
super()
this.value = a;
}
static of<A>(value: A): Try<A> { return new Success(value); }
map<B>(fn: (a: A) => B): Try<B> {
const val = this.value;
const x = $fn(fn)(val);
return mkTry<B>(x)
}
chain<B>(fn: (a: A) => Try<B>): Try<B> {
return fn(this.value);
}
orElseTry(fallback: Try<A>): Try<A> {
return this;
}
filterTry(fn: (a: A) => Boolean): Try<A> {
if (fn(this.value)) {
return this;
} else {
return mkTry(() => {throw new Error("NoSuchElementException")})
}
}
ap<B>(fnT: Try<Fn<A, B>>): Try<B> {
return this.chain(v => {
return fnT.map(fn => fn(v))
})
}
}
class Failure<A> extends Try<A> {
value: Error;
__tag: "Failure" = "Failure";
constructor(e: Error) {
super();
this.value = e;
}
static of<A>(value: Error): Try<A> { return new Failure(value); }
map<B>(fn: (a: A) => B): Try<B> {
return Failure.of(this.value);
}
chain<B>(fn: (a: A) => Try<B>): Try<B> {
return Failure.of(this.value)
}
orElseTry(fallback: Try<A>): Try<A> {
return fallback;
}
filterTry(fn: (a: A) => Boolean): Try<A> {
return this;
}
ap<B>(this: Try<A>,vT: Try<Fn<A,B>>): Try<B> {
throw this;
}
}
// maybe do it once
// const convertPromise = <A>(promise: Promise<A>, newPromiseProvider = Promise) => newPromiseProvider.resolve(promise)
// // Examples::
// // define a simple function that throws an error
const x = (a: number) => {
if (a % 2 === 0) {
return a
}
throw new Error(`value: ${a} is even`);
}
const div4 = (a: number) => {
if (a % 4 === 0) {
return a
}
throw new Error(`value: ${a} is not dividible by 4`);
}
// make it lazy
const lazyX = $fn(x)
const lazyDiv = $fn(div4)
// // wrap inside try, map chain and filter
const a1 = Try.lift(12)
.map(a => a + 10)
.map(b => (b * 2) - 4)
.chain(a => mkTry(lazyDiv(a)));
console.log(a1);
// const a2 = Try.liftFn(div4).apply(Try.lift(2)) //.apF(mkTry(() => 2))
// console.log(a2);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment