Skip to content

Instantly share code, notes, and snippets.

@kraftdorian
Last active March 9, 2023 14:50
Show Gist options
  • Save kraftdorian/81df8eb25377e171a366c908bbc47291 to your computer and use it in GitHub Desktop.
Save kraftdorian/81df8eb25377e171a366c908bbc47291 to your computer and use it in GitHub Desktop.
Maybe typeclass utils in TypeScript
type Fun<A, B> = (a: A) => B;
class Just<A> {
constructor(private readonly _value: A) {}
valueOf(): A {
return this._value;
}
}
class Nothing {
private static readonly id: unique symbol = Symbol('Nothing');
valueOf(): typeof Nothing.id {
return Nothing.id;
}
}
type Maybe<A> = Just<A> | Nothing;
/**
* fmap :: (a -> b) -> Maybe a -> Maybe b
*/
function fmap<A, B>(f: Fun<A, B>, a: Maybe<A>): Maybe<B> {
return a instanceof Nothing
? new Nothing() // fmap f Nothing = Nothing
: new Just<B>( // fmap f (Just x) = Just (f x)
f(a.valueOf())
);
}
/**
* Maybe Applicative namespace to be able to use string literal function names
*/
abstract class Applicative {
/**
* a -> Maybe a
* pure = Just
*/
static pure<A>(value: A): Maybe<A> {
return new Just<A>(value);
}
/**
* (<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b
*/
static '<*>'<A, B>(f: Maybe<Fun<A, B>>, a: Maybe<A>): Maybe<B> {
return f instanceof Nothing
? new Nothing()
: fmap<A, B>(f.valueOf(), a);
}
/**
* (<$>) :: (Functor f) => (a -> b) -> Maybe a -> Maybe b
* (<$>) = fmap
*/
static '<$>'<A, B>(f: Fun<A, B>, a: Maybe<A>): Maybe<B> {
return fmap<A, B>(f, a);
}
}
/**
* Maybe Monad namespace to be able to use string literal function names
*/
abstract class Monad {
/**
* return :: a -> Maybe a
* return = pure
*/
static return<A>(value: A): Maybe<A> {
return Applicative.pure<A>(value);
}
/**
* (>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
*/
static '>>='<A, B>(a: Maybe<A>, f: Fun<A, Maybe<B>>): Maybe<B> {
return a instanceof Nothing
? new Nothing()
: f(a.valueOf());
}
}
/**
* Maybe Monad instance to use in OOP
*/
class MonadInst<A> {
constructor(private readonly _value: Maybe<A>) {}
bind<B>(f: Fun<A, Maybe<B>>): MonadInst<B> {
return new MonadInst<B>(
Monad['>>='](this._value, f)
);
}
toMaybe(): Maybe<A> {
return this._value;
}
}
@bebrws
Copy link

bebrws commented Mar 8, 2023

Thank you! Love this implementation. Just a note to myself about how it could be used:

const one: MonadInst<number> = new MonadInst(new Just(1));
function multiplyByTwoIfGreaterThanZero(numVal: number): Maybe<number> {
    if (numVal > 0) {
        return new Just(numVal * 2);
    } else {
        return new Nothing();
    }
}
const two: Maybe<number> = one.bind(multiplyByTwoIfGreaterThanZero).toMaybe();
console.log(two.valueOf())

@kraftdorian
Copy link
Author

kraftdorian commented Mar 8, 2023

@bebrws I'm glad it's useful, even if it's not perfect - I saw some libs that were covering this and much more.
In general, I've had a few tries at understanding Monads by observing and embedding Haskell's approach into JS/TS:

I've had a great learning experience with the resources below:

Thanks for the feedback 🚀

@bebrws
Copy link

bebrws commented Mar 9, 2023 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment