├── .babelrc ├── .flowconfig ├── .gitignore ├── .npmignore ├── README.md ├── package.json ├── src ├── Fix.js └── schemes.js └── test ├── cata.tests.js └── example-data └── expression-ast.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "flow"] 3 | } 4 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [options] 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git 2 | node_modules 3 | src 4 | test 5 | tests 6 | .babelrc 7 | .flowconfig 8 | .gitignore 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | static-land-recursion-schemes 2 | ================== 3 | 4 | Recursion schemes library, compatible with flow-static-land. 5 | 6 | ## Installation 7 | ``` 8 | npm install static-land-recursion-schemes 9 | ``` 10 | 11 | ## Overview 12 | This provides a small core set of recursion schemes. Functions for composing schemes are not provided. Documentation is in-process, and will be written as a set of guides [here](https://medium.com/@JosephJnk/recursion-schemes-in-javascript-and-flow-with-static-land-recursision-schemes-97cf10599fb7). 13 | 14 | ## Provided schemes 15 | 16 | Exports from "static-land-recursion-schemes/lib/schemes": 17 | 18 | ``` 19 | type Algebra = HKT => A; 20 | function cata(Algebra, Fix, Functor) : A 21 | const catamorphism = cata; 22 | 23 | type Coalgebra = A => HKT; 24 | function ana(Coalgebra, A, Functor) : Fix 25 | const anamorphism = ana; 26 | 27 | type RAlgebra = (Fix, HKT) => A; 28 | function para(RAlgebra, Fix, Functor) : A 29 | const paramorphism = para; 30 | 31 | type RCoalgebra = A => HKT, A>>; 32 | function apo(RCoalgebra, A, Functor) : Fix 33 | const apomorphism = apo; 34 | 35 | function hylo(Algebra, Coalgebra, A, Functor) : B 36 | const hylomorphism = hylo; 37 | 38 | function zygo(Algebra, (HKT) => A, Fix, Functor) : A 39 | const zygomorphism = zygo; 40 | 41 | function gApo(Coalgebra, A => HKT>, A, Functor) : Fix 42 | const generalizedApomorphism = gApo; 43 | 44 | type NaturalTransformation = (HKT) => HKT; 45 | 46 | function prepro(NaturalTransformation, Algebra, Fix, Functor) : A 47 | const prepromorphism = prepro; 48 | 49 | function postpro(NaturalTransformation, Coalgebra, A, Functor) : Fix 50 | const postpromorphism = postpro; 51 | ``` 52 | 53 | Exports from "static-land-recursion-schemes/lib/Fix": 54 | 55 | ``` 56 | type Fix = In 57 | 58 | class In { 59 | term: HKT>; 60 | constructor(term: HKT>) { 61 | this.term = term; 62 | }; 63 | }; 64 | 65 | function out(term: Fix) : HKT> 66 | 67 | ``` 68 | 69 | ## License 70 | MIT 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static-land-recursion-schemes", 3 | "version": "1.0.0", 4 | "description": "Recursion schemes compatible with flow-static-land", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "build": "rimraf lib && babel src/ -d lib/ && flow-copy-source -v src lib", 8 | "test": "mocha --compilers js:babel-register" 9 | }, 10 | "keywords": [ 11 | "staticland", 12 | "recursion", 13 | "schemes", 14 | "morphisms" 15 | ], 16 | "author": "Joseph Junker", 17 | "repository": "https://github.com/JosephJNK/static-land-recursion-schemes", 18 | "license": "MIT", 19 | "dependencies": { 20 | "flow-static-land": "^0.2.6" 21 | }, 22 | "devDependencies": { 23 | "babel-cli": "^6.24.1", 24 | "babel-preset-es2015": "^6.24.1", 25 | "babel-preset-flow": "^6.23.0", 26 | "chai": "^4.0.2", 27 | "flow-bin": "^0.46.0", 28 | "flow-copy-source": "^1.2.0", 29 | "mocha": "^3.4.2", 30 | "rimraf": "^2.6.1" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Fix.js: -------------------------------------------------------------------------------- 1 | 2 | // @flow 3 | 4 | import type { HKT } from "flow-static-land/lib/HKT"; 5 | 6 | export type Fix = In; 7 | 8 | export class In { 9 | term: HKT>; 10 | constructor(term: HKT>) { 11 | this.term = term; 12 | }; 13 | }; 14 | 15 | export function out(term: Fix) : HKT> { 16 | return term.term; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/schemes.js: -------------------------------------------------------------------------------- 1 | 2 | // @flow 3 | 4 | import type { HKT } from "flow-static-land/lib/HKT"; 5 | import type { Functor } from "flow-static-land/lib/Functor"; 6 | import type { Either } from "flow-static-land/lib/Either"; 7 | 8 | import { either } from "flow-static-land/lib/Either"; 9 | 10 | import type { Fix } from "./Fix"; 11 | import { In, out } from "./Fix"; 12 | 13 | export type Algebra = HKT => A; 14 | 15 | export function cata(functor: Functor, 16 | transformer: Algebra, 17 | term: Fix) : A { 18 | const extracted = out(term), 19 | childrenMapped = functor.map(x => cata(functor, transformer, x), extracted), 20 | transformed = transformer(childrenMapped); 21 | 22 | return transformed; 23 | } 24 | export const catamorphism = cata; 25 | 26 | export type Coalgebra = A => HKT; 27 | 28 | export function ana(functor: Functor, 29 | transformer: Coalgebra, 30 | seed: A) : Fix { 31 | const transformed = transformer(seed), 32 | childrenMapped = functor.map(x => ana(functor, transformer, x), transformed), 33 | rewrapped = new In(childrenMapped); 34 | 35 | return rewrapped; 36 | } 37 | export const anamorphism = ana; 38 | 39 | export type RAlgebra = (Fix, HKT) => A; 40 | export function para(functor: Functor, 41 | transformer: RAlgebra, 42 | term: Fix) : A { 43 | const extracted = out(term), 44 | childrenMapped = functor.map(x => para(functor, transformer, x), extracted), 45 | transformed = transformer(term, childrenMapped); 46 | 47 | return transformed; 48 | } 49 | export const paramorphism = para; 50 | 51 | function id(a: A) : A { 52 | return a; 53 | } 54 | 55 | export type RCoalgebra = A => HKT, A>>; 56 | export function apo(functor: Functor, 57 | transformer: RCoalgebra, 58 | seed: A) : Fix { 59 | 60 | const transformed : HKT, A>> = transformer(seed); 61 | 62 | function fanIn(e: Either, A>) : Fix { 63 | return either(id, x => apo(functor, transformer, x), e); 64 | } 65 | 66 | const childrenMapped : HKT> = functor.map(fanIn, transformed); 67 | return new In(childrenMapped); 68 | } 69 | export const apomorphism = apo; 70 | 71 | export function hylo(functor: Functor, 72 | tearDown: Algebra, 73 | buildUp: Coalgebra, 74 | seed: A) : B { 75 | const built : HKT = buildUp(seed), 76 | recursed : HKT = functor.map((x : A) => hylo(functor, tearDown, buildUp, x), built), 77 | flattened : B = tearDown(recursed); 78 | 79 | return flattened; 80 | } 81 | export const hylomorphism = hylo; 82 | 83 | function zygoAlgebra(functor: Functor, 84 | alg: Algebra, 85 | almostAlgebra: (HKT) => A) : Algebra { 86 | return function(wrappedPair: HKT) : [A, B] { 87 | const secondWrapped : HKT = functor.map(pair => pair[1], wrappedPair), 88 | secondReduced : B = alg(secondWrapped), 89 | firstReduced : A = almostAlgebra(wrappedPair); 90 | 91 | return [firstReduced, secondReduced]; 92 | }; 93 | } 94 | 95 | export function zygo(functor: Functor, 96 | tearDown: Algebra, 97 | coalesce: (HKT) => A, 98 | term: Fix) : A { 99 | return cata(functor, zygoAlgebra(functor, tearDown, coalesce), term)[0]; 100 | }; 101 | export const zygomorphism = zygo; 102 | 103 | export function gApo(functor: Functor, 104 | expand: Coalgebra, 105 | expandA: A => HKT>, 106 | seed: A) : Fix { 107 | 108 | const expanded : HKT> = expandA(seed); 109 | 110 | function fanIn(e: Either) : Fix { 111 | return either(a => gApo(functor, expand, expandA, a), b => ana(functor, expand, b), e); 112 | } 113 | 114 | const transformed : HKT> = functor.map(fanIn, expanded); 115 | return new In(transformed); 116 | }; 117 | export const generalizedApomorphism = gApo; 118 | 119 | export type NaturalTransformation = (HKT) => HKT; 120 | 121 | export function prepro(functor: Functor, 122 | naturalTransformer: NaturalTransformation, 123 | transformer: Algebra, 124 | term: Fix) : A { 125 | const extracted : HKT> = out(term), 126 | childrenMapped : HKT = functor.map( 127 | x => prepro(functor, naturalTransformer, transformer, x), 128 | extracted), 129 | childrenMappedTransformed = naturalTransformer(childrenMapped), 130 | transformed : A = transformer(childrenMappedTransformed); 131 | 132 | return transformed; 133 | } 134 | export const prepromorphism = prepro; 135 | 136 | export function postpro(functor: Functor, 137 | naturalTransformer: NaturalTransformation, 138 | expand: Coalgebra, 139 | seed: A) : Fix { 140 | const expanded : HKT = expand(seed), 141 | naturalTransformed : HKT = naturalTransformer(expanded), 142 | childrenMapped = functor.map( 143 | x => postpro(functor, naturalTransformer, expand, x), 144 | naturalTransformed), 145 | rewrapped = new In(childrenMapped); 146 | 147 | return rewrapped; 148 | } 149 | export const postpromorphism = postpro; 150 | 151 | -------------------------------------------------------------------------------- /test/cata.tests.js: -------------------------------------------------------------------------------- 1 | 2 | // @flow 3 | 4 | declare var describe: Function; 5 | declare var it: Function; 6 | 7 | import { cata } from "../src/schemes"; 8 | 9 | import type { ExprF, Expr } from "./example-data/expression-ast"; 10 | 11 | import { assert } from "chai"; 12 | 13 | import { 14 | Plus, Times, Paren, Num, exprFunctor, 15 | prj, times, paren, num, plus 16 | } from "./example-data/expression-ast"; 17 | 18 | describe('catamorphism', () => { 19 | 20 | it('should let you fold a tree', () => { 21 | 22 | function _evalExpr (expression: ExprF) : number { 23 | const ex = prj(expression); 24 | return ( 25 | ex instanceof Plus ? ex.left + ex.right 26 | : ex instanceof Times ? ex.left * ex.right 27 | : ex instanceof Paren ? ex.contents 28 | : /* ex is a Num */ ex.value); 29 | } 30 | 31 | const evalExpr : Expr => number = ex => cata(exprFunctor, _evalExpr, ex); 32 | 33 | 34 | // AST for 2 * (1 + 1) * 4 * 3 35 | const expr = times(num(2), 36 | times(paren(plus(num(1), num(1))), 37 | times(num(4), num(3)))) 38 | 39 | assert.strictEqual(evalExpr(expr), 48); 40 | }); 41 | }); 42 | 43 | -------------------------------------------------------------------------------- /test/example-data/expression-ast.js: -------------------------------------------------------------------------------- 1 | 2 | // @flow 3 | 4 | import type { Functor } from "flow-static-land/lib/Functor"; 5 | import type { HKT } from "flow-static-land/lib/HKT"; 6 | import type { Fix } from "../../src/Fix"; 7 | import { In } from "../../src/Fix"; 8 | 9 | export type ExprI 10 | = Plus 11 | | Times 12 | | Paren 13 | | Num; 14 | 15 | export class Plus { 16 | left: A; 17 | right: A; 18 | constructor(left: A, right: A) { 19 | this.left = left; 20 | this.right = right; 21 | } 22 | }; 23 | 24 | export class Times { 25 | left: A; 26 | right: A; 27 | constructor(left: A, right: A) { 28 | this.left = left; 29 | this.right = right; 30 | } 31 | }; 32 | 33 | export class Paren { 34 | contents: A; 35 | constructor(contents: A) { 36 | this.contents = contents; 37 | } 38 | }; 39 | 40 | export class Num { 41 | value: number; 42 | constructor(value: number) { 43 | this.value = value; 44 | } 45 | }; 46 | 47 | export class IsExpr {}; 48 | export type ExprF = HKT; 49 | 50 | export function inj(a: ExprI) : ExprF { 51 | return ((a: any): ExprF); 52 | }; 53 | 54 | export function prj(a: ExprF) : ExprI { 55 | return ((a: any): ExprI); 56 | }; 57 | 58 | export function map(fn: (A) => B, a: ExprF) : ExprF { 59 | 60 | const node = prj(a); 61 | 62 | const mapped = ( 63 | node instanceof Plus ? new Plus(fn(node.left), fn(node.right)) 64 | : node instanceof Times ? new Times(fn(node.left), fn(node.right)) 65 | : node instanceof Paren ? new Paren(fn(node.contents)) 66 | : /* Num */ node); 67 | 68 | return inj(mapped); 69 | }; 70 | 71 | if (false) { 72 | ({map} : Functor) 73 | } 74 | 75 | export type Expr = Fix; 76 | 77 | export const exprFunctor = { map }; 78 | 79 | export function plus(left: Expr, right: Expr) : Expr { 80 | return new In(new Plus(left, right)); 81 | } 82 | 83 | export function times(left: Expr, right: Expr) : Expr { 84 | return new In(new Times(left, right)); 85 | } 86 | 87 | export function paren(contents: Expr) : Expr { 88 | return new In(new Paren(contents)); 89 | } 90 | 91 | export function num(value: number) : Expr { 92 | return new In(new Num(value)); 93 | } 94 | 95 | --------------------------------------------------------------------------------