diff options
author | elioat <hi@eli.li> | 2023-07-18 09:39:42 -0400 |
---|---|---|
committer | elioat <hi@eli.li> | 2023-07-18 09:39:42 -0400 |
commit | a937b952930728ada58dfa2ee7bbf6082d92b2fd (patch) | |
tree | 642ded36cb3401e9005e0146059ac3b0e2c070d0 | |
parent | 33b9336326bf1efd880cebde5aeeba967b69224f (diff) | |
download | tour-a937b952930728ada58dfa2ee7bbf6082d92b2fd.tar.gz |
*
-rw-r--r-- | js/functional.js | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/js/functional.js b/js/functional.js new file mode 100644 index 0000000..ec5a277 --- /dev/null +++ b/js/functional.js @@ -0,0 +1,462 @@ +// probably best not to *actually* use these, and instead check out +// +// https://folktale.origamitower.com/ +// https://ramdajs.com/ +// https://github.com/fantasyland + +// always :: a -> b -> a +const always = curry((a, b) => a); + +// compose :: ((y -> z), (x -> y), ..., (a -> b)) -> a -> z +const compose = (...fns) => (...args) => fns.reduceRight((res, fn) => [fn.call(null, ...res)], args)[0]; + +// curry :: ((a, b, ...) -> c) -> a -> b -> ... -> c +function curry(fn) { + const arity = fn.length; + + return function $curry(...args) { + if (args.length < arity) { + return $curry.bind(null, ...args); + } + + return fn.call(null, ...args); + }; +} + +// either :: (a -> c) -> (b -> c) -> Either a b -> c +const either = curry((f, g, e) => { + if (e.isLeft) { + return f(e.$value); + } + + return g(e.$value); +}); + +// identity :: x -> x +const identity = x => x; + +// inspect :: a -> String +const inspect = (x) => { + if (x && typeof x.inspect === 'function') { + return x.inspect(); + } + + function inspectFn(f) { + return f.name ? f.name : f.toString(); + } + + function inspectTerm(t) { + switch (typeof t) { + case 'string': + return `'${t}'`; + case 'object': { + const ts = Object.keys(t).map(k => [k, inspect(t[k])]); + return `{${ts.map(kv => kv.join(': ')).join(', ')}}`; + } + default: + return String(t); + } + } + + function inspectArgs(args) { + return Array.isArray(args) ? `[${args.map(inspect).join(', ')}]` : inspectTerm(args); + } + + return (typeof x === 'function') ? inspectFn(x) : inspectArgs(x); +}; + +// left :: a -> Either a b +const left = a => new Left(a); + +// liftA2 :: (Applicative f) => (a1 -> a2 -> b) -> f a1 -> f a2 -> f b +const liftA2 = curry((fn, a1, a2) => a1.map(fn).ap(a2)); + +// liftA3 :: (Applicative f) => (a1 -> a2 -> a3 -> b) -> f a1 -> f a2 -> f a3 -> f b +const liftA3 = curry((fn, a1, a2, a3) => a1.map(fn).ap(a2).ap(a3)); + +// maybe :: b -> (a -> b) -> Maybe a -> b +const maybe = curry((v, f, m) => { + if (m.isNothing) { + return v; + } + + return f(m.$value); +}); + +// nothing :: Maybe a +const nothing = Maybe.of(null); + +// reject :: a -> Task a b +const reject = a => Task.rejected(a); + + +const createCompose = curry((F, G) => class Compose { + constructor(x) { + this.$value = x; + } + + [util.inspect.custom]() { + return `Compose(${inspect(this.$value)})`; + } + + // ----- Pointed (Compose F G) + static of(x) { + return new Compose(F(G(x))); + } + + // ----- Functor (Compose F G) + map(fn) { + return new Compose(this.$value.map(x => x.map(fn))); + } + + // ----- Applicative (Compose F G) + ap(f) { + return f.map(this.$value); + } +}); + +class Either { + constructor(x) { + this.$value = x; + } + + // ----- Pointed (Either a) + static of(x) { + return new Right(x); + } +} + +class Left extends Either { + get isLeft() { + return true; + } + + get isRight() { + return false; + } + + static of(x) { + throw new Error('`of` called on class Left (value) instead of Either (type)'); + } + + [util.inspect.custom]() { + return `Left(${inspect(this.$value)})`; + } + + // ----- Functor (Either a) + map() { + return this; + } + + // ----- Applicative (Either a) + ap() { + return this; + } + + // ----- Monad (Either a) + chain() { + return this; + } + + join() { + return this; + } + + // ----- Traversable (Either a) + sequence(of) { + return of(this); + } + + traverse(of, fn) { + return of(this); + } +} + +class Right extends Either { + get isLeft() { + return false; + } + + get isRight() { + return true; + } + + static of(x) { + throw new Error('`of` called on class Right (value) instead of Either (type)'); + } + + [util.inspect.custom]() { + return `Right(${inspect(this.$value)})`; + } + + // ----- Functor (Either a) + map(fn) { + return Either.of(fn(this.$value)); + } + + // ----- Applicative (Either a) + ap(f) { + return f.map(this.$value); + } + + // ----- Monad (Either a) + chain(fn) { + return fn(this.$value); + } + + join() { + return this.$value; + } + + // ----- Traversable (Either a) + sequence(of) { + return this.traverse(of, identity); + } + + traverse(of, fn) { + fn(this.$value).map(Either.of); + } +} + +class Identity { + constructor(x) { + this.$value = x; + } + + [util.inspect.custom]() { + return `Identity(${inspect(this.$value)})`; + } + + // ----- Pointed Identity + static of(x) { + return new Identity(x); + } + + // ----- Functor Identity + map(fn) { + return Identity.of(fn(this.$value)); + } + + // ----- Applicative Identity + ap(f) { + return f.map(this.$value); + } + + // ----- Monad Identity + chain(fn) { + return this.map(fn).join(); + } + + join() { + return this.$value; + } + + // ----- Traversable Identity + sequence(of) { + return this.traverse(of, identity); + } + + traverse(of, fn) { + return fn(this.$value).map(Identity.of); + } +} + +class IO { + constructor(fn) { + this.unsafePerformIO = fn; + } + + [util.inspect.custom]() { + return 'IO(?)'; + } + + // ----- Pointed IO + static of(x) { + return new IO(() => x); + } + + // ----- Functor IO + map(fn) { + return new IO(compose(fn, this.unsafePerformIO)); + } + + // ----- Applicative IO + ap(f) { + return this.chain(fn => f.map(fn)); + } + + // ----- Monad IO + chain(fn) { + return this.map(fn).join(); + } + + join() { + return new IO(() => this.unsafePerformIO().unsafePerformIO()); + } +} + +class List { + constructor(xs) { + this.$value = xs; + } + + [util.inspect.custom]() { + return `List(${inspect(this.$value)})`; + } + + concat(x) { + return new List(this.$value.concat(x)); + } + + // ----- Pointed List + static of(x) { + return new List([x]); + } + + // ----- Functor List + map(fn) { + return new List(this.$value.map(fn)); + } + + // ----- Traversable List + sequence(of) { + return this.traverse(of, identity); + } + + traverse(of, fn) { + return this.$value.reduce( + (f, a) => fn(a).map(b => bs => bs.concat(b)).ap(f), + of(new List([])), + ); + } +} + +class Map { + constructor(x) { + this.$value = x; + } + + [util.inspect.custom]() { + return `Map(${inspect(this.$value)})`; + } + + insert(k, v) { + const singleton = {}; + singleton[k] = v; + return Map.of(Object.assign({}, this.$value, singleton)); + } + + reduceWithKeys(fn, zero) { + return Object.keys(this.$value) + .reduce((acc, k) => fn(acc, this.$value[k], k), zero); + } + + // ----- Functor (Map a) + map(fn) { + return this.reduceWithKeys( + (m, v, k) => m.insert(k, fn(v)), + new Map({}), + ); + } + + // ----- Traversable (Map a) + sequence(of) { + return this.traverse(of, identity); + } + + traverse(of, fn) { + return this.reduceWithKeys( + (f, a, k) => fn(a).map(b => m => m.insert(k, b)).ap(f), + of(new Map({})), + ); + } +} + +class Maybe { + get isNothing() { + return this.$value === null || this.$value === undefined; + } + + get isJust() { + return !this.isNothing; + } + + constructor(x) { + this.$value = x; + } + + [util.inspect.custom]() { + return this.isNothing ? 'Nothing' : `Just(${inspect(this.$value)})`; + } + + // ----- Pointed Maybe + static of(x) { + return new Maybe(x); + } + + // ----- Functor Maybe + map(fn) { + return this.isNothing ? this : Maybe.of(fn(this.$value)); + } + + // ----- Applicative Maybe + ap(f) { + return this.isNothing ? this : f.map(this.$value); + } + + // ----- Monad Maybe + chain(fn) { + return this.map(fn).join(); + } + + join() { + return this.isNothing ? this : this.$value; + } + + // ----- Traversable Maybe + sequence(of) { + return this.traverse(of, identity); + } + + traverse(of, fn) { + return this.isNothing ? of(this) : fn(this.$value).map(Maybe.of); + } +} + +class Task { + constructor(fork) { + this.fork = fork; + } + + [util.inspect.custom]() { + return 'Task(?)'; + } + + static rejected(x) { + return new Task((reject, _) => reject(x)); + } + + // ----- Pointed (Task a) + static of(x) { + return new Task((_, resolve) => resolve(x)); + } + + // ----- Functor (Task a) + map(fn) { + return new Task((reject, resolve) => this.fork(reject, compose(resolve, fn))); + } + + // ----- Applicative (Task a) + ap(f) { + return this.chain(fn => f.map(fn)); + } + + // ----- Monad (Task a) + chain(fn) { + return new Task((reject, resolve) => this.fork(reject, x => fn(x).fork(reject, resolve))); + } + + join() { + return this.chain(identity); + } +} \ No newline at end of file |