Last active
November 25, 2021 19:29
-
-
Save kraftdorian/b82fa095a698fc0047a5ecb2914361b2 to your computer and use it in GitHub Desktop.
TypeScript Monad implementation attempt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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