Skip to content

Instantly share code, notes, and snippets.

@hbina
Last active September 29, 2021 07:57
Show Gist options
  • Select an option

  • Save hbina/8410840e09e663ee4a0db93475f5eff5 to your computer and use it in GitHub Desktop.

Select an option

Save hbina/8410840e09e663ee4a0db93475f5eff5 to your computer and use it in GitHub Desktop.
Possible Implementation of MaybeNumber in TypeScript?
class MaybeNumber {
constructor(private readonly value?: number) {
}
// We could have used the constructor but it's not really nice to
// write `new` everywhere.
static create(n: number): MaybeNumber {
return new MaybeNumber(n);
}
static empty(): MaybeNumber {
return new MaybeNumber();
}
bind(f: (t: number) => MaybeNumber): MaybeNumber {
if (this.value) {
return f(this.value);
} else {
return MaybeNumber.empty();
}
}
valid(): boolean {
return this.value !== undefined;
}
}
// We can construct a MaybeNumber like thes
const maybeNumberA = MaybeNumber.create(10);
const maybeNumberB = MaybeNumber.empty();
// Function that transform a "normal" function into functions that accept MaybeNumbers
const liftN = (f: (...t: number[]) => number): ((...t: MaybeNumber[]) => MaybeNumber) => {
return (...t: MaybeNumber[]) => t.reduce((l, r) => l.bind(a => r.bind(b => MaybeNumber.create(f(a, b)))))
}
const addition = (l: number, r: number): number => l + r;
const subtraction = (l: number, r: number): number => l - r;
const divide = (l: number, r: number): MaybeNumber => r === 0 ? MaybeNumber.empty() : MaybeNumber.create(l / r);
const multiply = (l: number, r: number): MaybeNumber => l * r === Infinity || l * r === -Infinity ? MaybeNumber.empty() : MaybeNumber.create(l * r);
const liftedAddition = liftN(addition);
const liftedSubtraction = liftN(subtraction);
// Notice that the type of the result here is a MaybeNumber and _not_ a number.
// The type tells you, "hey, some of the computation you are about to do might fail"
// This way, less bug will sneak on you because every behavior (as far as nullability
// is concerned) is always explicit.
// However, I will admit that writing programs this way is not desirable
const maybeNumberC =
maybeNumberA.bind(
definitelyNumberA =>
maybeNumberB.bind(
definitelyNumberB =>
divide(definitelyNumberA, definitelyNumberB)));
// A possible convenient function that allows you to chain multiple fallible computations together?
const chain = (
computations: Array<((t: number) => MaybeNumber)>
): (t: number) => MaybeNumber => {
return (t) => computations.reduce(
(acc, curr) => acc.bind(curr),
MaybeNumber.create(t)
);
};
const either = (
left: (t: number) => MaybeNumber,
right: (t: number) => MaybeNumber
): (t: number) => MaybeNumber => {
return (t) => {
const leftResult = left(t);
const rightResult = right(t);
return leftResult.valid() ? leftResult : rightResult;
}
};
const or_else = (
left: (t: number) => MaybeNumber,
right: number
): (t: number) => MaybeNumber => {
return (t) => {
const leftResult = left(t);
return leftResult.valid() ? leftResult : MaybeNumber.create(right);
}
};
// Some dummy fallible functions
const isEven = (t: number): MaybeNumber => t % 2 === 0 ? MaybeNumber.create(t) : MaybeNumber.empty();
const isDivisibleBy5 = (t: number): MaybeNumber => t % 5 === 0 ? MaybeNumber.create(t) : MaybeNumber.empty()
const isDivisibleBy7 = (t: number): MaybeNumber => t % 7 === 0 ? MaybeNumber.create(t) : MaybeNumber.empty()
const isDivisibleBy8 = (t: number): MaybeNumber => t % 8 === 0 ? MaybeNumber.create(t) : MaybeNumber.empty();
// Create a program that accepts a number, perform some computation on it and then returns MaybeNumber
const program = chain([
either(chain([isEven, isDivisibleBy8]), chain([
isDivisibleBy5,
isDivisibleBy7
]))]);
console.clear();
// This should succeed
console.log(program(16))
console.log(program(24))
console.log(program(32))
// This should fail
console.log(program(4))
console.log(program(10))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment