├── .gitignore ├── arrow.js ├── cofree.js ├── examples.js ├── fantasy-tuples ├── README.md ├── fantasy-tuples.js ├── package.json └── src │ ├── nested.js │ └── tuples.js ├── index.js ├── list.js ├── nat.js ├── package.json ├── tree.js └── utils.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore all hidden dotfiles 2 | .* 3 | 4 | # Whitelist useful dotfiles 5 | !/.gitignore 6 | !/.travis.yml 7 | 8 | # Add a .gitkeep file in an empty 9 | # directory to check it in the repository 10 | !.gitkeep 11 | 12 | # Ignore modules 13 | /node_modules 14 | 15 | # Ignore logs 16 | *.log 17 | 18 | # Ignore code coverage reports 19 | coverage 20 | 21 | # local testing 22 | scratch 23 | 24 | # Legacy folders 25 | /git_modules 26 | -------------------------------------------------------------------------------- /arrow.js: -------------------------------------------------------------------------------- 1 | // (&&&) 2 | const ampersand = (f, g) => x => 3 | [f(x), g(x)] 4 | 5 | // (|||) 6 | const bars = (f, g) => e => 7 | e.fold(f, g) 8 | 9 | // (***) 10 | const stars = (f, g) => ([x, y]) => 11 | [f(x), g(y)] 12 | 13 | const funzip = ampersand(x => x.map(y => y[0]), 14 | x => x.map(y => y[1])) 15 | 16 | module.exports = {ampersand, bars, stars, funzip} 17 | -------------------------------------------------------------------------------- /cofree.js: -------------------------------------------------------------------------------- 1 | const daggy = require('daggy') 2 | const {id, inspect} = require('./utils') 3 | 4 | // data Cofree f a = Cofree a (f (Cofree f a)) 5 | const Cofree = daggy.tagged('head', 'tail') 6 | Cofree.prototype.map = function(f) { 7 | return Cofree(f(this.head), this.tail.map(x => x.map(f))) 8 | } 9 | Cofree.prototype.extend = function(f) { 10 | return Cofree(f(this), this.tail.map(t => t.extend(f))) 11 | } 12 | Cofree.prototype.duplicate = function() { 13 | return this.extend(id) 14 | } 15 | Cofree.prototype.extract = function() { 16 | return this.head 17 | } 18 | Cofree.prototype.inspect = function() { 19 | return `Cofree(${inspect(this.head)}, ${inspect(this.tail)})` 20 | 21 | } 22 | 23 | // s -> (s -> a) -> (s -> f s) -> Cofree f a 24 | Cofree.unfold = function(s, e, n) { 25 | return Cofree(e(s), n(s).map(s1 => Cofree.unfold(s1, e, n))) 26 | } 27 | 28 | module.exports = Cofree 29 | -------------------------------------------------------------------------------- /examples.js: -------------------------------------------------------------------------------- 1 | const Task = require('data.task') 2 | const Either = require('data.either') 3 | const {Left, Right} = Either 4 | const {Fix, Mu, comp, zip } = require('./index') 5 | const {inspect, id} = require('./utils') 6 | const List = require('./list') 7 | const {cons, nil, Cons, Nil} = List // cons, nil are wrapped in Fix 8 | const Nat = require('./nat') 9 | 10 | // Arbitrary Examples. Mostly on List, but works on any Fixed Point structure 11 | // ================== 12 | 13 | // Some usual definitions first... 14 | const mapList = f => t => 15 | t.cata({ 16 | Nil: () => nil, 17 | Cons: (x, xs) => cons(f(x), xs) 18 | }) 19 | 20 | const filterList = f => t => 21 | t.cata({ 22 | Nil: () => nil, 23 | Cons: (x, xs) => f(x) ? cons(x, xs) : xs 24 | }) 25 | 26 | const list = List.from([1, 2, 3]) 27 | 28 | 29 | // Basic usage 30 | // ================================== 31 | const sum = t => 32 | t.cata({ 33 | Nil: () => 0, 34 | Cons: (x, acc) => x + acc // <-- acc is the already finished children 35 | }) 36 | 37 | console.log('Basic') 38 | const result = list.cata(sum) 39 | console.log(result) 40 | // 6 41 | 42 | 43 | 44 | // Composition - one after the next in 1 pass 45 | // ================================== 46 | console.log('\n\nCOMP') 47 | const result1 = list.cata(comp(filterList(x => x > 1), mapList(x => x + 1))) 48 | console.log(List.to(result1)) 49 | // [ 4, 3, 2 ] 50 | 51 | 52 | 53 | // Zip - Two separate loops in 1 pass 54 | // ================================== 55 | console.log('\n\nZIP') 56 | const result2 = list.cata(zip(filterList(x => x > 1), mapList(x => x + 1))) 57 | console.log(result2) 58 | // [ Fix(Cons(3, Fix(Cons(2, Fix(Nil))))), 59 | // Fix(Cons(4, Fix(Cons(3, Fix(Cons(2, Fix(Nil))))))) ] 60 | 61 | // let's turn those into arrays... 62 | console.log(result2.map(List.to)) 63 | // [ [ 3, 2 ], [ 4, 3, 2 ] ] 64 | 65 | 66 | 67 | // Monadic - Here we're doing async 68 | // ================================== 69 | const delaySum = t => 70 | t.cata({ 71 | Nil: () => Task.of(0), 72 | Cons: (x, xs) => Task.of(x + xs) 73 | }) 74 | 75 | console.log('\n\nMonadic') 76 | list.cataM(Task.of, delaySum).fork(console.error, console.log) 77 | // 6 78 | 79 | 80 | 81 | // Para - Get the tail passed in 82 | // ================================== 83 | const factorial = t => 84 | t.cata({ 85 | Zero: () => 1, 86 | Succ: ([acc, tail]) => acc * (Nat.to(tail) + 1) // tail is the prev nat number 87 | }) 88 | 89 | console.log('\n\nPara') 90 | const ten = Nat.from(10) 91 | const result3 = ten.para(factorial) 92 | console.log(result3) 93 | // 3628800 94 | 95 | 96 | // Zygo - Get a second accumulator passed in via a helper fn 97 | // ================================== 98 | const toNum = t => 99 | t.cata({ 100 | Zero: () => 0, 101 | Succ: x => x + 1 102 | }) 103 | 104 | const factorial_ = t => 105 | t.cata({ 106 | Zero: () => 1, 107 | Succ: ({_1: tail, _2: acc}) => acc * (tail + 1) // tail is now an int 108 | }) 109 | 110 | console.log('\n\nZygo') 111 | const result4 = ten.zygo(toNum, factorial_) 112 | console.log(result4) 113 | // 3628800 114 | 115 | // Ana - unfold to build up a structure 116 | // ================================== 117 | 118 | const range = n => 119 | Fix.ana(x => x === 0 ? Nil : Cons(x, x-1), n) 120 | 121 | console.log('\n\nAna') 122 | const result5 = range(10) 123 | console.log(List.to(result5)) 124 | // [ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 ] 125 | 126 | // Histo - receive the history (previous acc and tail) in an annotation 127 | // ================================== 128 | const fib = t => 129 | t.cata({ 130 | Zero: () => 0, 131 | Succ: ({head, tail}) => 132 | tail.cata({ 133 | Zero: () => 1, 134 | Succ: ({head: head2}) => head + head2 135 | }) 136 | }) 137 | 138 | 139 | console.log('\n\nHisto') 140 | const result6 = ten.histo(fib) 141 | console.log(result6) 142 | // 55 143 | -------------------------------------------------------------------------------- /fantasy-tuples/README.md: -------------------------------------------------------------------------------- 1 | # Fantasy Tuples 2 | 3 | ![](https://raw.github.com/puffnfresh/fantasy-land/master/logo.png) 4 | 5 | ## General 6 | 7 | Tuples are another way of storing multiple values in a single value. 8 | They have a fixed number of elements (immutable), and so you can't 9 | cons to a tuple. 10 | Elements of a tuple do not need to be all of the same type! 11 | 12 | Example usage: 13 | 14 | ```javascript 15 | var tuples = require('fantasy-tuples'), 16 | Tuple2 = tuples.Tuple2; 17 | 18 | Tuple2(1, 2)._1; // 1 19 | ``` 20 | 21 | ## Testing 22 | 23 | ### Library 24 | 25 | Fantasy Options uses [nodeunit](https://github.com/caolan/nodeunit) for 26 | all the tests and because of this there is currently an existing 27 | [adapter](test/lib/test.js) in the library to help with integration 28 | between nodeunit and Fantasy Check. 29 | 30 | ### Coverage 31 | 32 | Currently Fantasy Check is using [Istanbul](https://github.com/gotwarlost/istanbul) 33 | for code coverage analysis; you can run the coverage via the following 34 | command: 35 | 36 | _This assumes that you have istanbul installed correctly._ 37 | 38 | ``` 39 | istanbul cover nodeunit -- test/*.js 40 | ``` 41 | -------------------------------------------------------------------------------- /fantasy-tuples/fantasy-tuples.js: -------------------------------------------------------------------------------- 1 | const nested = require('./src/nested'); 2 | const tuples = require('./src/tuples'); 3 | 4 | module.exports = { nested 5 | , Tuple: nested.Tuple 6 | , tuples 7 | , Tuple2: tuples.Tuple2 8 | , Tuple3: tuples.Tuple3 9 | , Tuple4: tuples.Tuple4 10 | , Tuple5: tuples.Tuple5 11 | }; 12 | -------------------------------------------------------------------------------- /fantasy-tuples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "_args": [ 3 | [ 4 | { 5 | "raw": "fantasy-tuples", 6 | "scope": null, 7 | "escapedName": "fantasy-tuples", 8 | "name": "fantasy-tuples", 9 | "rawSpec": "", 10 | "spec": "latest", 11 | "type": "tag" 12 | }, 13 | "/Users/blonsdorf/Documents/design-system-internal" 14 | ] 15 | ], 16 | "_from": "fantasy-tuples@latest", 17 | "_id": "fantasy-tuples@1.0.0", 18 | "_inCache": true, 19 | "_location": "/fantasy-tuples", 20 | "_nodeVersion": "6.9.1", 21 | "_npmOperationalInternal": { 22 | "host": "packages-12-west.internal.npmjs.com", 23 | "tmp": "tmp/fantasy-tuples-1.0.0.tgz_1481210939994_0.8167012750636786" 24 | }, 25 | "_npmUser": { 26 | "name": "joneshf", 27 | "email": "jones3.hardy@gmail.com" 28 | }, 29 | "_npmVersion": "3.10.9", 30 | "_phantomChildren": {}, 31 | "_requested": { 32 | "raw": "fantasy-tuples", 33 | "scope": null, 34 | "escapedName": "fantasy-tuples", 35 | "name": "fantasy-tuples", 36 | "rawSpec": "", 37 | "spec": "latest", 38 | "type": "tag" 39 | }, 40 | "_requiredBy": [ 41 | "#USER" 42 | ], 43 | "_resolved": "https://registry.npmjs.org/fantasy-tuples/-/fantasy-tuples-1.0.0.tgz", 44 | "_shasum": "1b407e993bdc8131218473080691c7327fe63fbb", 45 | "_shrinkwrap": null, 46 | "_spec": "fantasy-tuples", 47 | "_where": "/Users/blonsdorf/Documents/design-system-internal", 48 | "author": { 49 | "name": "Brian McKenna" 50 | }, 51 | "bugs": { 52 | "url": "https://github.com/puffnfresh/fantasy-tuples/issues" 53 | }, 54 | "dependencies": { 55 | "daggy": "0.0.x", 56 | "fantasy-land": "git+https://github.com/fantasyland/fantasy-land.git" 57 | }, 58 | "description": "Tuple data structures.", 59 | "devDependencies": { 60 | "fantasy-check": "0.1.6", 61 | "fantasy-combinators": "0.0.x", 62 | "fantasy-equality": "git+https://github.com/fantasyland/fantasy-equality.git", 63 | "fantasy-helpers": "0.0.x", 64 | "nodeunit": "0.9.x" 65 | }, 66 | "directories": {}, 67 | "dist": { 68 | "shasum": "1b407e993bdc8131218473080691c7327fe63fbb", 69 | "tarball": "https://registry.npmjs.org/fantasy-tuples/-/fantasy-tuples-1.0.0.tgz" 70 | }, 71 | "files": [ 72 | "fantasy-tuples.js", 73 | "src/*.js" 74 | ], 75 | "gitHead": "3283c3a4d7fd4cb413965f37107803553a0765b5", 76 | "homepage": "https://github.com/puffnfresh/fantasy-tuples#readme", 77 | "keywords": [ 78 | "fantasyland", 79 | "tuples", 80 | "semigroup" 81 | ], 82 | "license": "MIT", 83 | "main": "fantasy-tuples.js", 84 | "maintainers": [ 85 | { 86 | "name": "joneshf", 87 | "email": "jones3.hardy@gmail.com" 88 | }, 89 | { 90 | "name": "puffnfresh", 91 | "email": "brian@brianmckenna.org" 92 | }, 93 | { 94 | "name": "stickupkid", 95 | "email": "stickupkid@gmail.com" 96 | }, 97 | { 98 | "name": "trevorbasinger", 99 | "email": "trevor.basinger@gmail.com" 100 | } 101 | ], 102 | "name": "fantasy-tuples", 103 | "optionalDependencies": {}, 104 | "readme": "ERROR: No README data found!", 105 | "repository": { 106 | "type": "git", 107 | "url": "git://github.com/puffnfresh/fantasy-tuples.git" 108 | }, 109 | "scripts": { 110 | "test": "node --harmony_destructuring node_modules/.bin/nodeunit test/*.js" 111 | }, 112 | "version": "1.0.0" 113 | } 114 | -------------------------------------------------------------------------------- /fantasy-tuples/src/nested.js: -------------------------------------------------------------------------------- 1 | const {tagged} = require('daggy'); 2 | const {map, extend, extract} = require('fantasy-land'); 3 | const Tuple = tagged('_1', '_2'); 4 | 5 | const inspect = t => 6 | t && t.inspect ? t.inspect() : t 7 | 8 | Tuple.prototype.inspect = function() { 9 | return `Tuple(${inspect(this._1)}, ${inspect(this._2)})`; 10 | }; 11 | 12 | // Methods 13 | Tuple.prototype.dimap = function(f, g) { 14 | return Tuple(f(this._1), g(this._2)); 15 | }; 16 | Tuple.prototype.map = function(f) { 17 | return Tuple(this._1, f(this._2)); 18 | }; 19 | Tuple.prototype.curry = function(f) { 20 | return f(this); 21 | }; 22 | Tuple.prototype.uncurry = function(f) { 23 | return f(this._1, this._2); 24 | }; 25 | Tuple.prototype.extend = function(f) { 26 | return Tuple(this._1, f(this)); 27 | }; 28 | Tuple.prototype.duplicate = function() { 29 | return this.extend(x => x) 30 | }; 31 | Tuple.prototype.extract = function() { 32 | return this._2; 33 | }; 34 | Tuple.prototype.foldl = function(f, z) { 35 | return f(this._2, z); 36 | }; 37 | Tuple.prototype.foldr = function(f, z) { 38 | return f(z, this._2); 39 | }; 40 | Tuple.prototype.foldMap = function(f, p) { 41 | return f(this._2); 42 | }; 43 | 44 | const tuple2 = Tuple; 45 | const tuple3 = (a, b, c) => Tuple(tuple2(a, b), c); 46 | const tuple4 = (a, b, c, d) => Tuple(tuple3(a, b, c), d); 47 | const tuple5 = (a, b, c, d, e) => Tuple(tuple4(a, b, c, d), e); 48 | 49 | const curry2 = (f, a, b) => f(tuple2(a, b)); 50 | const curry3 = (f, a, b, c) => f(tuple3(a, b, c)); 51 | const curry4 = (f, a, b, c, d) => f(tuple4(a, b, c, d)); 52 | const curry5 = (f, a, b, c, d, e) => f(tuple5(a, b, c, d, e)); 53 | 54 | const uncurry2 = (f, t) => f(t._1, t._2); 55 | const uncurry3 = (f, t) => f(t._1._1, t._1._2, t._2); 56 | const uncurry4 = (f, t) => f(t._1._1._1, t._1._1._2, t._1._2, t._2); 57 | const uncurry5 = (f, t) => f(t._1._1._1._1, t._1._1._1._2, t._1._1._2, t._1._2, t._2); 58 | 59 | module.exports = { Tuple, tuple2, tuple3, tuple4, tuple5 60 | , curry2, curry3, curry4, curry5 61 | , uncurry2, uncurry3, uncurry4, uncurry5 62 | }; 63 | -------------------------------------------------------------------------------- /fantasy-tuples/src/tuples.js: -------------------------------------------------------------------------------- 1 | const daggy = require('daggy'); 2 | const {Tuple} = require('./nested'); 3 | const {concat} = require('fantasy-land'); 4 | 5 | const Tuple2 = Tuple; 6 | const Tuple3 = daggy.tagged('_1', '_2', '_3'); 7 | const Tuple4 = daggy.tagged('_1', '_2', '_3', '_4'); 8 | const Tuple5 = daggy.tagged('_1', '_2', '_3', '_4', '_5'); 9 | 10 | // Methods 11 | Tuple2.prototype[concat] = function(b) { 12 | return Tuple2( 13 | this._1[concat](b._1), 14 | this._2[concat](b._2) 15 | ); 16 | }; 17 | Tuple3.prototype[concat] = function(b) { 18 | return Tuple3( 19 | this._1[concat](b._1), 20 | this._2[concat](b._2), 21 | this._3[concat](b._3) 22 | ); 23 | }; 24 | Tuple4.prototype[concat] = function(b) { 25 | return Tuple4( 26 | this._1[concat](b._1), 27 | this._2[concat](b._2), 28 | this._3[concat](b._3), 29 | this._4[concat](b._4) 30 | ); 31 | }; 32 | Tuple5.prototype[concat] = function(b) { 33 | return Tuple5( 34 | this._1[concat](b._1), 35 | this._2[concat](b._2), 36 | this._3[concat](b._3), 37 | this._4[concat](b._4), 38 | this._5[concat](b._5) 39 | ); 40 | }; 41 | 42 | module.exports = {Tuple, Tuple2, Tuple3, Tuple4, Tuple5}; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const daggy = require('daggy') 2 | const Either = require('data.either') 3 | const {Tuple} = require('./fantasy-tuples') 4 | const Cofree = require('./cofree') 5 | const {id, compose, inspect} = require('./utils') 6 | const {ampersand, bars, stars, funzip} = require('./arrow') 7 | 8 | const zip = (f, g) => compose(stars(f, g), funzip) 9 | 10 | const comp = (f, g) => compose(f, compose(unfix, g)) 11 | 12 | //hylo :: Functor f => (f b -> b) -> (a -> f a) -> a -> b 13 | const hylo = (f, g, t) => 14 | f(g(t).map(x => hylo(f, g, x))) 15 | 16 | // fold :: Recursive t => (Base t a -> a) -> t -> a 17 | const fold = (f, x) => x.cata(f) 18 | 19 | // unfold :: Corecursive t => (a -> Base t a) -> a -> t 20 | const unfold = (f, x) => x.ana(f) 21 | 22 | //ana g = a where a = embed . fmap a . g 23 | const ana = t => (g, x) => 24 | t.embed(g(x).map(y => ana(t)(g, y))) 25 | 26 | const cata = (f, x) => x.cata(f) 27 | 28 | // apo :: Fixpoint f t => (a -> f (Either a t)) -> a -> t 29 | const apo = t => (coa, x) => 30 | t.embed(coa(x).map(y => 31 | y.fold(x => apo(t)(coa, x), 32 | x => x))) 33 | 34 | // refold :: Functor f => (f b -> b) -> (a -> f a) -> a -> b 35 | const refold = hylo 36 | 37 | // cataM algM = algM <=< (mapM (cataM algM) . unFix) 38 | const cataM = (of, algM) => m => 39 | m.project() 40 | .traverse(of, x => x.cataM(of, algM)) 41 | .chain(algM) 42 | 43 | // we use cata here! 44 | // t is an unfixed algebra whos children are done 45 | const para = point => (alg, x) => 46 | x.cata(ampersand(alg, t => 47 | point(t.map(y => y[1]))))[0] 48 | 49 | 50 | //type GAlgebra w f a = f (w a) → a 51 | // 52 | // gcata 53 | // ∷ ∀ f t w a 54 | // . (Recursive t f, Comonad w) 55 | // ⇒ DistributiveLaw f w 56 | // → GAlgebra w f a 57 | // → t 58 | // → a 59 | // gcata k g = g <<< extract <<< go 60 | // where 61 | // go t = k $ map (duplicate <<< map g <<< go) (project t) 62 | // 63 | const gcata = (k, g) => x => { 64 | const go = t => 65 | k(t.project().map(y => go(y).map(g).duplicate())) 66 | 67 | return g(go(x).extract()) 68 | } 69 | // go must return a comonad 70 | // t is fix or mu i guess 71 | // 72 | // 73 | // type ElgotAlgebra w f a = w (f a) → a 74 | // 75 | // elgotCata 76 | // ∷ ∀ f t w a 77 | // . (Recursive t f, Comonad w) 78 | // ⇒ DistributiveLaw f w 79 | // → ElgotAlgebra w f a 80 | // → t 81 | // → a 82 | // elgotCata k g = g <<< go 83 | // where 84 | // go t = k $ map (map g <<< duplicate <<< go) (project t) 85 | const elgotCata = (k, g) => x => { 86 | const go = t => 87 | k(t.project().map(y => go(y).duplicate().map(g))) 88 | 89 | return g(go(x)) 90 | } 91 | 92 | // CATA VIA Elgot 93 | 94 | //const distCata = m => 95 | // Id.of(m.map(x => x.extract())) // same 96 | 97 | ////our alg expects w (f a) 98 | //const ecata = (alg, t) => 99 | // elgotCata(distCata, x => alg(x.extract()))(t) 100 | 101 | 102 | 103 | // CATA VIA GCATA 104 | 105 | // const distCata = m => 106 | // Id.of(m.map(x => x.extract())) // this extract traverses the Id 107 | 108 | // our alg expects Cons(x, Id(acc)), but we extract in advance here 109 | // const cata = (alg, t) => 110 | // gcata(distCata, x => alg(x.map(y => y.extract())))(t) 111 | 112 | //type DistributiveLaw f w = ∀ a. f (w a) → w (f a) 113 | //distZygo ∷ ∀ f a. Functor f ⇒ Algebra f a → DistributiveLaw f (Tuple a) 114 | //distZygo g m = Tuple (g (map fst m)) (map snd m) 115 | const distZygo = alg => m => 116 | Tuple(alg(m.map(x => x._1)), m.map(x => x._2)) 117 | // NOTE: since it returns a Tuple of the acc and Nil, the children will too! 118 | 119 | // we distribute the f (w a) -> w (f a) 120 | // alg is the aux fn 121 | // m is our actual pattern functor holding a tuple: 122 | // 123 | // m.map on Nil does nothing so our alg gets passed Nil, then it is 124 | // wrapped up in a Tuple(acc, Nil) via distZygo itself. 125 | // So this is the result of go() in gcata, which is a Comonad. 126 | // Now. we're finished our "to the bottom' recursion in the middle of 127 | // go() and we must map the zalg (pass in _.2 from on our Tuple) to 128 | // transform the children in _2 from Cons/Nil to acc2 129 | // Then we duplicate it because we want _2 to hold the whole 130 | // tuple since it is what goes into zalg at the end of the day. 131 | // Finally, on our last iteration, we end up with our children finished 132 | // and we want to pass our entire pattern functor we extract() the _2 and call zalg again 133 | 134 | const zygo = (alg, zalg, x) => 135 | gcata(distZygo(alg), zalg)(x) 136 | 137 | // > algZygo :: Functor f => 138 | // > (f b -> b) -> 139 | // > (f (a, b) -> a) -> 140 | // > f (a, b) -> (a, b) 141 | // > algZygo f g = g &&& f . fmap snd 142 | //const algZygo = (f, g) => ampersand(g, compose(f, x => x.map(snd))) 143 | // > zygo :: Functor f => 144 | // > (f b -> b) -> (f (a, b) -> a) -> Fix f -> a 145 | // > zygo f g = fst . cata (algZygo f g) 146 | // const zygo = (f, g, x) => 147 | // fst(cata(algZygo(f, g), x)) 148 | // para but passes it to an f 149 | // mutu but doesn't pass [acc1, acc2] to both 150 | 151 | const unfix = x => x.project() 152 | 153 | // distGHisto 154 | // ∷ ∀ f h 155 | // . (Functor f, Functor h) 156 | // ⇒ DistributiveLaw f h 157 | // → DistributiveLaw f (Cofree h) 158 | // distGHisto k x = unfoldCofree x (map extract) (k <<< map tail) 159 | const distGHisto = (k, x) => 160 | Cofree.unfold(x, 161 | y => y.map(_y => _y.extract()), 162 | r => k(r.map(x => x.tail))) 163 | 164 | const distHisto = x => distGHisto(id, x) 165 | 166 | const histo = (alg, x) => gcata(distHisto, alg)(x) 167 | 168 | //newtype Fix f = Fix (f (Fix f)) 169 | const Fix = f => ({ 170 | f, 171 | project: () => f, 172 | embed: Fix, 173 | inspect: () => 174 | `Fix(${inspect(f)})`, 175 | cata: g => 176 | g(f.map(x => x.cata(g))), 177 | para: alg => para(Fix)(alg, Fix(f)), 178 | zygo: (aux, alg) => zygo(aux, alg, Fix(f)), 179 | histo: alg => histo(alg, Fix(f)), 180 | cataM: (of, alg) => cataM(of, alg)(Fix(f)) 181 | }) 182 | 183 | Fix.embed = Fix 184 | Fix.ana = ana(Fix) 185 | Fix.apo = apo(Fix) 186 | Fix.to = t => Fix.ana(id, t) 187 | Fix.from = t => t.cata(id) 188 | 189 | // f is a pattern functor. Embed church encodes the fold inside the Mu 190 | //newtype Mu f = Mu (forall a. (f a -> a) -> a) 191 | const Mu = f => 192 | ({ 193 | project: () => Mu(f).cata(y => y.map(Mu.embed)), 194 | cata: g => f(g), 195 | para: alg => para(Mu)(alg, Mu(f)), 196 | zygo: (aux, alg) => zygo(aux, alg, Mu(f)), 197 | cataM: (of, alg) => cataM(of, alg)(Mu(f)) 198 | }) 199 | 200 | // cata calls f (the fn in Mu), so this is recursive. 201 | Mu.embed = m => 202 | Mu(f => f(m.map(x => x.cata(f)))) 203 | 204 | Mu.ana = ana(Mu) 205 | 206 | 207 | 208 | module.exports = { 209 | Fix, 210 | Mu, 211 | ana, 212 | cata, 213 | gcata, 214 | para, 215 | cataM, 216 | zip, 217 | comp, 218 | zygo, 219 | histo, 220 | elgotCata, 221 | apo, 222 | unfix 223 | } 224 | 225 | -------------------------------------------------------------------------------- /list.js: -------------------------------------------------------------------------------- 1 | const daggy = require('daggy') 2 | const Either = require('data.either') 3 | const Task = require('data.task') 4 | const {Left, Right} = Either 5 | const {Fix, ana, apo, cata, zygo, gcata, para, cataM, algProd, algCoProd, Mu, unfix} = require('./index') 6 | const {inspect, id, compose} = require('./utils') 7 | 8 | const ListF = daggy.taggedSum({Nil: [], Cons: ['x', 'xs']}) 9 | const {Nil, Cons} = ListF 10 | 11 | ListF.prototype.inspect = function(f) { 12 | return this.cata({ 13 | Nil: () => 'Nil', 14 | Cons: (x, xs) => `Cons(${inspect(x)}, ${inspect(xs)})` 15 | }) 16 | } 17 | 18 | ListF.prototype.map = function(f) { 19 | return this.cata({ 20 | Nil: () => Nil, 21 | Cons: (x, xs) => Cons(x, f(xs)) 22 | }) 23 | } 24 | 25 | ListF.prototype.traverse = function(of, f) { 26 | return this.cata({ 27 | Nil: () => of(Nil), 28 | Cons: (x, xs) => f(xs).map(ys => Cons(x, ys)) 29 | }) 30 | } 31 | 32 | const from = xs => 33 | xs.reduce((acc, x) => cons(x, acc), nil) 34 | 35 | const to = l => 36 | l.cata(t => 37 | t.cata({ 38 | Nil: () => [], 39 | Cons: (x, xs) => [x].concat(xs) 40 | })) 41 | 42 | const nil = Fix(Nil) 43 | const cons = (x, xs) => Fix(Cons(x, xs)) 44 | 45 | ////console.log(para(tails, l1)) 46 | //const munil = Mu.embed(Nil) 47 | //const mucons = (x, xs) => Mu.embed(Cons(x, xs)) 48 | //const arrToList_ = xs => 49 | // xs.reduce((acc, x) => mucons(x, acc), munil) 50 | 51 | //const mul1 = arrToList_([1,2,3]) 52 | 53 | //console.log(mul1) 54 | //console.log('sum', mul1.cata(sum)) 55 | 56 | module.exports = {nil, cons, Nil, Cons, from, to} 57 | -------------------------------------------------------------------------------- /nat.js: -------------------------------------------------------------------------------- 1 | const daggy = require('daggy') 2 | const {Fix, ana} = require('./index') 3 | const {inspect} = require('./utils') 4 | 5 | const Nat = daggy.taggedSum({Succ: ['x'], Zero: []}) 6 | const {Succ, Zero} = Nat 7 | Nat.prototype.inspect = function() { 8 | return this.cata({ 9 | Zero: () => 'Zero', 10 | Succ: x => `Succ(${inspect(x)})` 11 | }) 12 | 13 | } 14 | Nat.prototype.map = function(f) { 15 | return this.cata({ 16 | Zero: () => Zero, 17 | Succ: x => Succ(f(x)) 18 | }) 19 | } 20 | 21 | const from = n => 22 | ana(Fix)(y => y < 1 ? Zero : Succ(y-1), n) 23 | 24 | const to = nat => 25 | nat.cata(t => 26 | t.cata({ 27 | Zero: () => 0, 28 | Succ: x => x + 1 29 | })) 30 | 31 | // Seems like we're missing an implicit coercion from the type family 32 | 33 | //console.log(para(fact, ten)) 34 | 35 | //console.log(cata(toNum, ten)) 36 | // 37 | //> fib :: Integer -> Integer 38 | //> fib = histo f where 39 | //> f :: NatF (Ann NatF Integer) -> Integer 40 | //> f Zero = 0 41 | //> f (Succ (unAnn -> (Zero,_))) = 1 42 | //> f (Succ (unAnn -> (Succ (unAnn -> (_,n)),m))) = m + n 43 | 44 | //Succ holds an annotation which looks like: 45 | // Cofree(2, Succ(Cofree(1, Succ(Cofree(1, Succ(Cofree(0, Zero))))))) 46 | // That is, head is 2, tail is the rest. It's like para, but with the result too 47 | const fib = t => 48 | t.cata({ 49 | Zero: () => 0, 50 | Succ: ann => 51 | ann.tail.cata({ 52 | Zero: () => 1, 53 | Succ: an => an.head + ann.head 54 | }) 55 | }) 56 | 57 | // const ten = ana(Fix)(y => y < 1 ? Zero : Succ(y-1), 10) 58 | // console.log(histo(fib, ten)) 59 | 60 | module.exports = {Nat, from, to} 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "recursion-schemes", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "scripts": { 6 | "test": "echo \"Error: no test specified\" && exit 1" 7 | }, 8 | "author": "", 9 | "license": "ISC", 10 | "keywords": [], 11 | "description": "", 12 | "dependencies": { 13 | "daggy": "0.0.1", 14 | "data.either": "^1.4.0", 15 | "data.task": "^3.1.1", 16 | "fantasy-tuples": "^1.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tree.js: -------------------------------------------------------------------------------- 1 | const daggy = require('daggy') 2 | const Tree = daggy.taggedSum({Empty: [], Leaf: ['x'], Node: ['x', 'children']}) 3 | const {Node, Leaf, Empty} = Tree 4 | const {Fix} = require('./index') 5 | 6 | // This is a forest. Not exactly sure if Tree a [Tree] is a proper fix type though. Works for the most part in my tests. 7 | Tree.prototype.map = function(f) { 8 | return this.cata({ 9 | Empty: () => Empty, 10 | Leaf: x => Leaf(x), 11 | Node: (x, xs) => Node(x, xs.map(f)) 12 | }) 13 | } 14 | 15 | Tree.prototype.inspect = function(f) { 16 | return this.cata({ 17 | Empty: () => 'Empty', 18 | Leaf: x => `Leaf(${inspect(x)})`, 19 | Node: (x, xs) => `Node(${inspect(x)}, ${xs})` 20 | }) 21 | } 22 | 23 | // (a -> b) -> Tree a -> Tree b 24 | const mapTree = f => t => 25 | t.cata({ 26 | Empty: () => Fix(Empty), 27 | Leaf: x => Fix(Leaf(f(x))), 28 | Node: (x, xs) => Fix(Node(f(x), xs)) 29 | }) 30 | 31 | // natural transformation 32 | // Tree a -> [a] 33 | const fromTree = t => 34 | t.cata({ 35 | Empty: () => [], 36 | Leaf: x => [x], 37 | Node: (x, xs) => [x].concat(xs) 38 | }) 39 | 40 | module.exports = {Tree, mapTree, fromTree} 41 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | const inspect = t => 2 | t && t.inspect ? t.inspect() : t 3 | 4 | const id = x => x 5 | const compose = (f, g) => x => f(g(x)) 6 | 7 | module.exports = {id, compose, inspect} 8 | --------------------------------------------------------------------------------