Skip to content

Instantly share code, notes, and snippets.

@Heimdell
Created November 25, 2017 15:11
Show Gist options
  • Select an option

  • Save Heimdell/15af881a7e1c02effd588695eec5a6b5 to your computer and use it in GitHub Desktop.

Select an option

Save Heimdell/15af881a7e1c02effd588695eec5a6b5 to your computer and use it in GitHub Desktop.
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())
/*
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