Last active
September 29, 2021 07:57
-
-
Save hbina/8410840e09e663ee4a0db93475f5eff5 to your computer and use it in GitHub Desktop.
Possible Implementation of MaybeNumber in TypeScript?
This file contains hidden or 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
| 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