Created
November 25, 2017 15:11
-
-
Save Heimdell/15af881a7e1c02effd588695eec5a6b5 to your computer and use it in GitHub Desktop.
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
| var same = x => x | |
| /* | |
| data Either e a = Ok { value: a } | Err { msg: e } | |
| This means, the "Either e a" (for any types "a" and "e") | |
| is the type of either: | |
| - type Ok, containing field "value" of type "a"; | |
| - type Err, containing field "msg" of type "e". | |
| Its used when function can return either result or some error message. | |
| "e" and "a" are the type-parameters. For instance, if you create a | |
| "Either.return(1)", it will have type "Either e Number". | |
| */ | |
| class Either { | |
| // Either e a -> String | |
| toString() { | |
| return this.match( | |
| value => `Ok { value = ${value} }`, | |
| msg => `Err { msg = ${msg} }` | |
| ) | |
| } | |
| // () should be a type with only 1 value | |
| // since we're in js, undefined will suffice | |
| // | |
| // Boolean -> Either () () | |
| static bool(bool) { | |
| return bool? Either.return() : Either.throw() | |
| } | |
| // implementing Bifunctor | |
| // so we can map success and failure cases independently | |
| // and they won't mess up (Promises can't do that) | |
| // | |
| // (this: Either e a, a -> b, e -> f) -> Either b f | |
| bimap(f, g) { | |
| return this.match( | |
| value => Either.return(f(value)), | |
| msg => Either.throw (g(msg)) | |
| ) | |
| } | |
| // implementing Functor | |
| // change the result value if any | |
| // don't change state (Ok -> Ok, Err -> Err) | |
| // | |
| // (this: Either e a, a -> b) -> Either b e | |
| map(f) { | |
| return this.bimap(f, same) | |
| } | |
| // implementing Foldable | |
| // its a reduceRight, a canonical operation | |
| // | |
| // (this: Either e a, b, (a, b) -> b) -> n | |
| foldr(acc, reducer) { | |
| return this.match( | |
| value => reducer(value, acc), | |
| _ => acc | |
| ) | |
| } | |
| // implementing Monad | |
| // a -> Either e a | |
| static return(value) { | |
| return new Ok(value) | |
| } | |
| // still implementing Monad | |
| // (this: Either e a, a -> Either e b) -> Either e b | |
| then(callback) { | |
| return this.match(callback, Either.throw) | |
| } | |
| // implementing MonadThrow | |
| // e -> Either e a | |
| static throw(value) { | |
| return new Err(value) | |
| } | |
| // implementing MonadCatch | |
| // (this: Either e a, e -> Either f a) -> Either f a | |
| catch(callback) { | |
| return this.match(Either.return, callback) | |
| } | |
| } | |
| // these two classes are simple: ctor & pattern-matcher | |
| class Ok extends Either { | |
| constructor(value) { super(); this.value = value } | |
| match(onOk, onErr) { return onOk(this.value) } | |
| } | |
| class Err extends Either { | |
| constructor(msg) { super(); this.msg = msg } | |
| match(onOk, onErr) { return onErr(this.msg) } | |
| } | |
| var doThings = status => | |
| Either.bool(status == 200).bimap( | |
| _ => "Good", | |
| _ => "Bad" | |
| ) | |
| console.log(doThings(201).toString()) |
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
| /* | |
| Closes approx to Algrebraic Data Types I can make in js. | |
| It will extend given "base" class for purposes of typecheck, if given one. | |
| The call... | |
| > var Pair = adt(null, "Pair", "fst", "snd") | |
| ... will assign to "Pair" a class called "Pair". | |
| The call... | |
| > var pair = Pair.of(1, "a") | |
| ... will assign the "pair" an object "Pair { fst: 1, snd: 'a' }". | |
| "pair.toArray()" will return its constructor args in the same order | |
| they were given. | |
| */ | |
| var adt = (base, ctor, ...args) => { | |
| var classGen = eval(` | |
| base => { | |
| class ${ctor} extends (base || Object) { | |
| constructor(${args}) { | |
| super() | |
| Object.assign(this, {${args}}) | |
| } | |
| static of(${args}) { | |
| return new ${ctor}(${args}) | |
| } | |
| toArray() { | |
| let {${args}} = this | |
| return [${args}] | |
| } | |
| }; | |
| return ${ctor} | |
| } | |
| `) | |
| return classGen(base) | |
| } | |
| /* | |
| This is the closest thing to "match/case ... with/of ..." | |
| pattern-match operators in (truly!) functional languages. | |
| > var p = Pair.of(1, "a") | |
| > var r = match(p, { | |
| > Pair: (a, b) => a + b.length | |
| > }) | |
| It only matches objects capable of ".toArray()". | |
| */ | |
| var match = (o, paths) => { | |
| if (!o.toArray) { | |
| throw Error(`Cannon match non-ADT object: ${o}`) | |
| } | |
| var ctor = o.constructor | |
| var path = paths[ctor.name] | |
| if (path === undefined) { | |
| path = paths["def"] | |
| } | |
| if (path === undefined) { | |
| throw Error(`No matching paths for ${o}`) | |
| } | |
| if (path instanceof Function) { | |
| return path.apply(null, o.toArray()) | |
| } else { | |
| return path | |
| } | |
| } | |
| // For cases when you want just pattern-match your argument | |
| // and don't need it as whole. | |
| var func = paths => o => match(o, paths) | |
| //// TESTING GROUNDS /////////////////////////////////////////////////////////// | |
| // So we can check that something is list. | |
| class List {} | |
| // List is either empty... | |
| var Nil = adt(List, "Nil") | |
| // ... or an appendage of element to another list. | |
| var Cons = adt(List, "Cons", "x", "xs") | |
| /* | |
| These functions below are direct ports from haskell. | |
| Haskell is lazy and optimised for recursion, and js is not. | |
| So, I don't give a fuck to the fact they eat stack like crazy. | |
| */ | |
| var length1 = func( | |
| { Cons: (x, xs) => 1 + length(xs) | |
| , Nil: () => 0 | |
| } | |
| ) | |
| var sum1 = func( | |
| { Cons: (x, xs) => x + sum(xs) | |
| , Nil: () => 0 | |
| } | |
| ) | |
| var map = f => reduceRight( | |
| Nil.of(), | |
| (x, xs) => Cons.of(f(x), xs) | |
| ) | |
| // analoguous to Array#reduceRight | |
| var reduceRight = (zero, add) => | |
| function aux(list) { | |
| return match(list, | |
| { Cons: (x, xs) => add(x, aux(xs)) | |
| , Nil: () => zero | |
| } | |
| ) | |
| } | |
| var sum2 = reduceRight(0, (x, y) => x + y) | |
| var length2 = reduceRight(0, (_, l) => 1 + l) | |
| var list = Cons.of(1, Cons.of(2, Nil.of())) | |
| console.log(sum2(map(x => x + 1)(list))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment