Skip to content

Instantly share code, notes, and snippets.

@kraftdorian
Last active November 25, 2021 19:29
Show Gist options
  • Save kraftdorian/b82fa095a698fc0047a5ecb2914361b2 to your computer and use it in GitHub Desktop.
Save kraftdorian/b82fa095a698fc0047a5ecb2914361b2 to your computer and use it in GitHub Desktop.
TypeScript Monad implementation attempt
//
// Created with help of the great contents from:
// http://learnyouahaskell.com/a-fistful-of-monads
//
// Just a helper type to define exact type of a basic function with one input and output.
type BasicFn<A, B> = (input: A) => B;
// Wrapper interface for Monad valueOf return value, to differentiate from other return types.
interface IMonadValueOf<A> {
type: typeof MonadValueOfStructType;
value: A;
};
// Basic Monad interface
interface IMonad<A> {
/**
* It's like function application, only instead of taking a normal value and feeding it to a normal function,
* it takes a monadic value (that is, a value with a context) and feeds it to a function
* that takes a normal value but returns a monadic value.
*/
bind: <B>(f: BasicFn<A, IMonad<B>>) => IMonad<B>;
/**
* It takes a value and puts it in a minimal default context that still holds that value.
* In other words, it takes something and wraps it in a monad
*/
lift: <B>(f: BasicFn<A, B>) => IMonad<B>;
// Helper function to be able to reach the inner value.
valueOf: () => IMonadValueOf<A>;
}
// Unique symbol used in Monad valueOf helper function to indicate the unique object structure.
const MonadValueOfStructType: unique symbol = Symbol('Struct.Monad.ValueOf');
// Helper function that creates Monad valueOf object that wraps the value
function createMonadValueOfStruct<A>(value: A): IMonadValueOf<A> {
return Object.freeze<IMonadValueOf<A>>({
type: MonadValueOfStructType,
value
});
}
// Basic Monad implementation
function Monad<A>(input: A): IMonad<A> {
return {
bind<B>(f: BasicFn<A, IMonad<B>>): IMonad<B> {
return f(input);
},
lift<B>(f: BasicFn<A, B>): IMonad<B> {
return Monad(f(input));
},
valueOf(): IMonadValueOf<A> {
return createMonadValueOfStruct(input);
}
};
}
// ==== TEST ====
// Left identity
(function testLeftIdentity() {
function add(x: number, y: number): number {
return x + y;
}
const x = 3;
const f = add.bind(null, 100000);
const left = Monad(x).lift(f).valueOf();
const right = Monad(f(x)).valueOf();
console.log({
name: 'testLeftIdentity',
isSuccess: left.value === right.value
});
})();
// Right identity
(function testRightIdentity() {
const x = 'hello there!';
const left = Monad(x).lift(y => y).valueOf();
const right = Monad(x).valueOf();
console.log({
name: 'testRightIdentity',
isSuccess: left.value === right.value
});
})();
// Associativity
(function testAssociativity() {
function multiply(x: number, y: number): IMonad<number> {
return Monad(x * y);
}
function add(x: number, y: number): IMonad<number> {
return Monad(x + y);
}
const x = 1;
const f = multiply.bind(null, 10);
const g = add.bind(null, 2);
const left = (Monad(x).bind(f)).bind(g).valueOf();
const right = Monad(x).bind(y => f(y).bind(g)).valueOf();
console.log({
name: 'testAssociativity',
isSuccess: left.value === right.value
})
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment