├── .babelrc ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── src ├── data │ ├── either.js │ ├── future.js │ ├── index.js │ └── maybe.js ├── functions │ ├── __curry.js │ ├── ap.js │ ├── apply.js │ ├── chain.js │ ├── compose.js │ ├── curry.js │ ├── curryN.js │ ├── either.js │ ├── equals.js │ ├── every.js │ ├── flip.js │ ├── handle_error.js │ ├── identity.js │ ├── index.js │ ├── keys.js │ ├── lift_a2.js │ ├── lift_a3.js │ ├── lift_a4.js │ ├── lift_a5.js │ ├── map.js │ ├── maybe.js │ ├── once.js │ ├── to_pairs.js │ ├── trampoline.js │ ├── type_of.js │ ├── values.js │ └── zip.js └── index.js └── test ├── .eslintrc.json ├── data ├── either.test.js ├── future.test.js └── maybe.test.js └── functions ├── ap.test.js ├── apply.test.js ├── chain.test.js ├── compose.test.js ├── curry.test.js ├── curryN.test.js ├── either.test.js ├── equals.test.js ├── every.test.js ├── flip.test.js ├── handle_error.test.js ├── identity.test.js ├── keys.test.js ├── lift_a2.test.js ├── lift_a3.test.js ├── lift_a4.test.js ├── lift_a5.test.js ├── map.test.js ├── maybe.test.js ├── once.test.js ├── private_curry.test.js ├── to_pairs.test.js ├── trampoline.test.js ├── type_of.test.js ├── values.test.js └── zip.test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | coverage/* 3 | dist/* 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "node": true 5 | }, 6 | "rules": { 7 | "import/prefer-default-export": "off", 8 | "no-underscore-dangle": "off", 9 | "new-cap": 0 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | dist 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6' 4 | 5 | notifications: 6 | email: false 7 | 8 | cache: 9 | directories: 10 | - node_modules 11 | 12 | before_script: npm prune 13 | 14 | script: 15 | - npm run precommit 16 | 17 | after_success: 18 | - "./node_modules/.bin/nyc report --reporter=text-lcov | ./node_modules/.bin/coveralls" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/matthewglover/functionaljs.svg?branch=master)](https://travis-ci.org/matthewglover/functionaljs) [![Coverage Status](https://coveralls.io/repos/github/matthewglover/functionaljs/badge.svg?branch=compose)](https://coveralls.io/github/matthewglover/functionaljs?branch=compose) 2 | 3 | # FunctionalJS 4 | 5 | 6 | ## What 7 | 8 | A functional JavaScript library to learn about functional programming. 9 | 10 | ## Why 11 | 12 | There are great libraries like Ramda and Folktale, but it's good to understand what's going on under the hood. 13 | 14 | ## How 15 | 16 | Using ES6 and Tape for testing. 17 | 18 | ## Features: 19 | 20 | 21 | ### Functions 22 | 23 | - `ap :: Functor (a -> b) -> Functor a -> Functor b` 24 | - `apply :: (a... -> b) -> [a...] -> b` 25 | - `chain :: (a -> M b) -> M a -> M b` 26 | - `compose :: (f -> g, ..., a -> b) -> a -> g` 27 | - `curry :: ((a, b, ..., f) => g) => a => b => ... f => g` (up to arity of 6 uses curryN internally, then uses __curry) 28 | - `curryN :: ((a, b, ..., f) => g, n) => a => b => ... f => g` (up to arity of 6 - then uses __curry) 29 | - `either :: (a -> c) -> (b -> c) -> Either a b -> c` 30 | - `equals :: a -> b -> Boolean` 31 | - `every :: (a -> Boolean) -> [a] -> Boolean` 32 | - `flip :: (a, ... b -> c) -> (b..., a -> c)` (up to 6 arity, flipped function curried) 33 | - `handleError :: (e -> c) -> (a -> b) -> a -> (b | c)` 34 | - `identity :: a -> a` 35 | - `keys :: {String: *} -> [String]` 36 | - `liftA2 :: (a -> b -> c) -> Functor a -> Functor b -> Functor c` 37 | - `liftA3 :: (a -> b -> c -> d) -> Functor a -> Functor b -> Functor c -> Functor d` 38 | - `liftA4 :: (a -> b -> c -> d -> e) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e` 39 | - `liftA5 :: (a -> b -> c -> d -> e -> f) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e -> Functor f` 40 | - `map :: (a -> b) -> F a -> F b` 41 | - `maybe :: b -> (a -> b) -> Maybe a -> b` 42 | - `once :: (a... -> b) -> (a... -> b)` 43 | - `toPairs :: {String: *} -> [[String, *]]` 44 | - `trampoline :: (*... -> a) -> (*... -> a) - requires recursive call to be wrapped in a thunk trampoline.Thunk(() => [return value])` 45 | - `typeOf :: * -> String` 46 | - `values :: {String: *} -> [*]` 47 | - `zip :: {String: *} -> [[String, *]]` 48 | 49 | ### Data 50 | 51 | #### Maybe 52 | 53 | ##### Static methods / properties 54 | - `Maybe.of - a -> Just a` 55 | - `Maybe.Nothing -> Nothing` 56 | 57 | ##### Instance methods 58 | 59 | - `Maybe::ap - Maybe (a -> b) ~> Maybe a -> Maybe b` 60 | - `Maybe::chain - Maybe a ~> (a -> Maybe b) -> Maybe b` 61 | - `Maybe::equals - Maybe a ~> (Maybe b) -> Boolean` 62 | - `Maybe::isJust - Maybe a ~> () -> Boolean` 63 | - `Maybe::isNothing - Maybe a ~> () -> Boolean` 64 | - `Maybe::map - Maybe a ~> (a -> b) -> Maybe b` 65 | 66 | 67 | #### Either 68 | 69 | ##### Static methods 70 | - `Either.of - b -> Right b` 71 | - `Either.Left - a -> Left a` 72 | 73 | ##### Instance methods 74 | 75 | - `Either::ap - Either a (b -> c) ~> Either b -> Either a c` 76 | - `Either::chain - Either a b ~> (b -> Either a c) -> Either a c` 77 | - `Either::map - Either a b ~> (b -> c) -> Either a c` 78 | - `Either::isLeft - Either a b ~> () -> Boolean` 79 | - `Either::isRight - Either a b ~> () -> Boolean` 80 | 81 | 82 | #### Future 83 | 84 | ##### Static methods 85 | - `Future.of - a -> Future e a` 86 | - `Future.reject - e -> Future e` 87 | 88 | ##### Instance methods 89 | - `Future::fork - Future e a ~> (e -> (), a -> ()) -> ()` 90 | - `Future::map - Future e a ~> (a -> b) -> Future e b` 91 | - `Future::chain - Future e a ~> (a -> Future b) -> Future e b` 92 | - `Future::ap - Future e (a -> b) ~> Future e a -> Future e b` 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "functionaljs", 3 | "version": "1.0.0", 4 | "description": "A functional js library to learn about functional programming", 5 | "main": "index.js", 6 | "scripts": { 7 | "coverage": "nyc check-coverage --lines 100, --functions 100, --branches 100", 8 | "build": "babel --out-dir dist src", 9 | "lint": "eslint './**/*.js'", 10 | "prebuild": "rimraf dist", 11 | "precommit": "npm run lint && nyc npm test && npm run coverage", 12 | "report": "nyc report --reporter=lcov", 13 | "test": "tape './test/**/*.js' | tap-dot", 14 | "watch": "nodemon -q -x 'npm test'" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/matthewglover/functionaljs.git" 19 | }, 20 | "author": "Matt Glover (https://github.com/matthewglover)", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/matthewglover/functionaljs/issues" 24 | }, 25 | "homepage": "https://github.com/matthewglover/functionaljs#readme", 26 | "devDependencies": { 27 | "babel-cli": "^6.18.0", 28 | "babel-eslint": "^7.1.0", 29 | "babel-preset-es2015": "^6.18.0", 30 | "babel-preset-stage-2": "^6.18.0", 31 | "coveralls": "^2.11.14", 32 | "eslint": "^3.9.0", 33 | "eslint-config-airbnb": "^12.0.0", 34 | "eslint-plugin-import": "^2.1.0", 35 | "eslint-plugin-jsx-a11y": "^2.2.3", 36 | "eslint-plugin-react": "^6.4.1", 37 | "husky": "^0.11.9", 38 | "nyc": "^8.3.2", 39 | "rimraf": "^2.5.4", 40 | "sinon": "^1.17.6", 41 | "tap-dot": "^1.0.5", 42 | "tape": "^4.6.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/data/either.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable constructor-super, no-use-before-define, class-methods-use-this */ 2 | const { compose } = require('../functions'); 3 | 4 | class Either { 5 | 6 | constructor(v) { 7 | this.__value = v; 8 | } 9 | 10 | isLeft() { 11 | return this instanceof Left; 12 | } 13 | 14 | isRight() { 15 | return this instanceof Right; 16 | } 17 | } 18 | 19 | Either.Right = x => Right.of(x); 20 | 21 | Either.Left = x => Left.of(x); 22 | 23 | Either.of = Either.Right; 24 | 25 | class Right extends Either { 26 | 27 | map(f) { 28 | return this.chain(compose(Right.of, f)); 29 | } 30 | 31 | chain(f) { 32 | return f(this.__value); 33 | } 34 | 35 | ap(ex) { 36 | return ex.map(this.__value); 37 | } 38 | } 39 | 40 | Right.of = x => new Right(x); 41 | 42 | class Left extends Either { 43 | 44 | map() { 45 | return this; 46 | } 47 | 48 | chain() { 49 | return this; 50 | } 51 | 52 | ap() { 53 | return this; 54 | } 55 | } 56 | 57 | Left.of = x => new Left(x); 58 | 59 | module.exports = { 60 | Either, 61 | Right, 62 | Left, 63 | }; 64 | -------------------------------------------------------------------------------- /src/data/future.js: -------------------------------------------------------------------------------- 1 | const { compose, handleError, once } = require('../functions'); 2 | 3 | 4 | class Future { 5 | 6 | constructor(f) { 7 | this.__f = f; 8 | } 9 | 10 | map(f) { 11 | return this.chain(compose(Future.of, f)); 12 | } 13 | 14 | chain(f) { 15 | return new Future((reject, resolve) => 16 | this.fork( 17 | reject, 18 | handleError(reject, x => f(x).fork(reject, resolve)))); 19 | } 20 | 21 | ap(fx) { 22 | return new Future((reject, resolve) => { 23 | let isRejected = false; 24 | let fnReady = false; 25 | let valueReady = false; 26 | let fn; 27 | let value; 28 | 29 | const rejectOnce = once((error) => { 30 | isRejected = true; 31 | reject(error); 32 | }); 33 | 34 | const tryRun = () => { 35 | if (fnReady && valueReady) { 36 | resolve(fn(value)); 37 | } 38 | }; 39 | 40 | const resolveFn = (f) => { 41 | if (isRejected) return; 42 | fnReady = true; 43 | fn = f; 44 | tryRun(); 45 | }; 46 | 47 | const resolveVal = (v) => { 48 | if (isRejected) return; 49 | valueReady = true; 50 | value = v; 51 | tryRun(); 52 | }; 53 | 54 | this.fork(rejectOnce, handleError(rejectOnce, resolveFn)); 55 | fx.fork(rejectOnce, handleError(rejectOnce, resolveVal)); 56 | }); 57 | } 58 | 59 | fork(reject, resolve) { 60 | this.__f(reject, resolve); 61 | } 62 | } 63 | 64 | Future.of = x => new Future((reject, resolve) => resolve(x)); 65 | 66 | Future.reject = error => new Future(reject => reject(error)); 67 | 68 | 69 | module.exports = Future; 70 | -------------------------------------------------------------------------------- /src/data/index.js: -------------------------------------------------------------------------------- 1 | const Future = require('./future'); 2 | const { Maybe, Just, Nothing } = require('./maybe'); 3 | const { Either, Right, Left } = require('./either'); 4 | 5 | module.exports = { 6 | Future, 7 | Maybe, 8 | Just, 9 | Nothing, 10 | Either, 11 | Right, 12 | Left, 13 | }; 14 | -------------------------------------------------------------------------------- /src/data/maybe.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable constructor-super, no-use-before-define, class-methods-use-this */ 2 | const { equals, compose } = require('../functions'); 3 | 4 | class Maybe { 5 | 6 | isNothing() { 7 | return this instanceof Nothing; 8 | } 9 | 10 | isJust() { 11 | return this instanceof Just; 12 | } 13 | } 14 | 15 | class Just extends Maybe { 16 | 17 | constructor(v) { 18 | super(); 19 | this.__value = v; 20 | } 21 | 22 | map(f) { 23 | return this.chain(compose(Maybe.of, f)); 24 | } 25 | 26 | ap(mb) { 27 | return mb.map(this.__value); 28 | } 29 | 30 | chain(f) { 31 | return f(this.__value); 32 | } 33 | 34 | equals(mb) { 35 | return equals(this.__value, mb.__value); 36 | } 37 | } 38 | 39 | class Nothing extends Maybe { 40 | 41 | map() { 42 | return this; 43 | } 44 | 45 | chain() { 46 | return this; 47 | } 48 | 49 | ap() { 50 | return this; 51 | } 52 | 53 | equals(mx) { 54 | return mx.isNothing(); 55 | } 56 | } 57 | 58 | Maybe.of = x => 59 | (x === null || x === undefined 60 | ? Maybe.Nothing 61 | : new Just(x)); 62 | 63 | Maybe.Nothing = new Nothing(); 64 | 65 | module.exports = { 66 | Maybe, 67 | Just, 68 | Nothing, 69 | }; 70 | -------------------------------------------------------------------------------- /src/functions/__curry.js: -------------------------------------------------------------------------------- 1 | 2 | // __curry :: ((a, b, ..., f) => g) => a => b => ... f => g 3 | const __curry = (f, ...args) => 4 | (args.length >= f.length 5 | ? f(...args) 6 | : (...moreArgs) => __curry(f, ...args.concat(moreArgs))); 7 | 8 | module.exports = __curry; 9 | -------------------------------------------------------------------------------- /src/functions/ap.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | 3 | // ap :: Functor (a -> b) -> Functor a -> Functor b 4 | const ap = curry((fa, fb) => fa.ap(fb)); 5 | 6 | module.exports = ap; 7 | -------------------------------------------------------------------------------- /src/functions/apply.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | 3 | 4 | // apply :: (a... -> b) -> [a...] -> b 5 | const apply = curry((f, args) => f(...args)); 6 | 7 | module.exports = apply; 8 | -------------------------------------------------------------------------------- /src/functions/chain.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | 3 | // chain :: (a -> M b) -> M a -> M b 4 | const chain = curry((f, M) => M.chain(f)); 5 | 6 | module.exports = chain; 7 | -------------------------------------------------------------------------------- /src/functions/compose.js: -------------------------------------------------------------------------------- 1 | 2 | // simpleCompose :: (b -> c, a -> b) -> a -> c 3 | const simpleCompose = (f, g) => x => f(g(x)); 4 | 5 | // compose :: (g -> h, ..., a -> b) -> a -> h 6 | const compose = (...fns) => fns.reduce(simpleCompose); 7 | 8 | module.exports = compose; 9 | -------------------------------------------------------------------------------- /src/functions/curry.js: -------------------------------------------------------------------------------- 1 | const __curry = require('./__curry'); 2 | const curryN = require('./curryN'); 3 | 4 | 5 | const curry = f => 6 | (f.length <= 6 7 | ? curryN(f.length, f) 8 | : __curry(f)); 9 | 10 | module.exports = curry; 11 | -------------------------------------------------------------------------------- /src/functions/curryN.js: -------------------------------------------------------------------------------- 1 | const __curry = require('./__curry'); 2 | const identity = require('./identity'); 3 | 4 | const curry0 = identity; 5 | 6 | const curry1 = f => function inner(a) { 7 | return (arguments.length >= 1 8 | ? f(a) 9 | : curry1(f)); 10 | }; 11 | 12 | // NOTE: must use function to use arguments.length 13 | const curry2 = f => function inner(a, b) { 14 | switch (arguments.length) { 15 | case 0: return curry2(f); 16 | case 1: return curry1(bb => f(a, bb)); 17 | default: return f(a, b); 18 | } 19 | }; 20 | 21 | const curry3 = f => function inner(a, b, c) { 22 | switch (arguments.length) { 23 | case 0: return curry3(f); 24 | case 1: return curry2((bb, cc) => f(a, bb, cc)); 25 | case 2: return curry1(cc => f(a, b, cc)); 26 | default: return f(a, b, c); 27 | } 28 | }; 29 | 30 | const curry4 = f => function inner(a, b, c, d) { 31 | switch (arguments.length) { 32 | case 0: return curry4(f); 33 | case 1: return curry3((bb, cc, dd) => f(a, bb, cc, dd)); 34 | case 2: return curry2((cc, dd) => f(a, b, cc, dd)); 35 | case 3: return curry1(dd => f(a, b, c, dd)); 36 | default: return f(a, b, c, d); 37 | } 38 | }; 39 | 40 | const curry5 = f => function inner(a, b, c, d, e) { 41 | switch (arguments.length) { 42 | case 0: return curry5(f); 43 | case 1: return curry4((bb, cc, dd, ee) => f(a, bb, cc, dd, ee)); 44 | case 2: return curry3((cc, dd, ee) => f(a, b, cc, dd, ee)); 45 | case 3: return curry2((dd, ee) => f(a, b, c, dd, ee)); 46 | case 4: return curry1(ee => f(a, b, c, d, ee)); 47 | default: return f(a, b, c, d, e); 48 | } 49 | }; 50 | 51 | const curry6 = f => function inner(a, b, c, d, e, g) { 52 | switch (arguments.length) { 53 | case 0: return curry6(f); 54 | case 1: return curry5((bb, cc, dd, ee, gg) => f(a, bb, cc, dd, ee, gg)); 55 | case 2: return curry4((cc, dd, ee, gg) => f(a, b, cc, dd, ee, gg)); 56 | case 3: return curry3((dd, ee, gg) => f(a, b, c, dd, ee, gg)); 57 | case 4: return curry2((ee, gg) => f(a, b, c, d, ee, gg)); 58 | case 5: return curry1(gg => f(a, b, c, d, e, gg)); 59 | default: return f(a, b, c, d, e, g); 60 | } 61 | }; 62 | 63 | const curryFns = { 64 | curry0, 65 | curry1, 66 | curry2, 67 | curry3, 68 | curry4, 69 | curry5, 70 | curry6, 71 | }; 72 | 73 | const curryN = (n, f) => 74 | (n <= 6 75 | ? curryFns[`curry${n}`](f) 76 | : __curry(f)); 77 | 78 | module.exports = curryN; 79 | -------------------------------------------------------------------------------- /src/functions/either.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | 3 | 4 | // either :: (a -> c) -> (b -> c) -> Either a b -> c 5 | const either = curry((f, g, ab) => 6 | (ab.isLeft() 7 | ? f(ab.__value) 8 | : g(ab.__value))); 9 | 10 | module.exports = either; 11 | -------------------------------------------------------------------------------- /src/functions/equals.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-use-before-define */ 2 | const curry = require('./curry'); 3 | const zip = require('./zip'); 4 | const typeOf = require('./type_of'); 5 | const apply = require('./apply'); 6 | const keys = require('./keys'); 7 | const every = require('./every'); 8 | const toPairs = require('./to_pairs'); 9 | 10 | 11 | // arrayEquals :: [a] -> [b] -> Boolean 12 | const arrayEquals = (as, bs) => 13 | as.length === bs.length && 14 | every(apply(equals), zip(as, bs)); 15 | 16 | 17 | // objectEquals :: a -> b -> Boolean 18 | const objectEquals = (ao, bo) => { 19 | const valuesEqual = ([k, v]) => equals(bo[k], v); 20 | 21 | return ( 22 | keys(ao).length === keys(bo).length && 23 | every(valuesEqual, toPairs(ao))); 24 | }; 25 | 26 | // equals :: a -> b -> Boolean 27 | const equals = curry((a, b) => { 28 | if (typeOf(a) !== typeOf(b)) return false; 29 | if (typeOf(a) === 'array') return arrayEquals(a, b); 30 | if (typeOf(a) === 'object') return objectEquals(a, b); 31 | return a === b; 32 | }); 33 | 34 | module.exports = equals; 35 | -------------------------------------------------------------------------------- /src/functions/every.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | 3 | 4 | // every :: (a -> Boolean) -> [a] -> Boolean 5 | const every = curry((predicate, array) => array.every(predicate)); 6 | 7 | module.exports = every; 8 | -------------------------------------------------------------------------------- /src/functions/flip.js: -------------------------------------------------------------------------------- 1 | const curryN = require('./curryN'); 2 | 3 | // flip :: (a, ... b -> c) -> (b..., a -> c) 4 | const flip = (f) => { 5 | const flipped = (...args) => f(...args.reverse()); 6 | 7 | // Curry flipped function if possible 8 | return f.length <= 6 9 | ? curryN(f.length, flipped) 10 | : flipped; 11 | }; 12 | 13 | module.exports = flip; 14 | -------------------------------------------------------------------------------- /src/functions/handle_error.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | 3 | // handleError :: (e -> c) -> (a -> b) -> a -> (b | c) 4 | const handleError = curry((errorHandler, f, x) => { 5 | try { 6 | return f(x); 7 | } catch (error) { 8 | return errorHandler(error); 9 | } 10 | }); 11 | 12 | module.exports = handleError; 13 | -------------------------------------------------------------------------------- /src/functions/identity.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // identity :: a => a 4 | const identity = x => x; 5 | 6 | module.exports = identity; 7 | -------------------------------------------------------------------------------- /src/functions/index.js: -------------------------------------------------------------------------------- 1 | const ap = require('./ap'); 2 | const apply = require('./apply'); 3 | const chain = require('./chain'); 4 | const compose = require('./compose'); 5 | const curry = require('./curry'); 6 | const curryN = require('./curryN'); 7 | const either = require('./either'); 8 | const equals = require('./equals'); 9 | const every = require('./every'); 10 | const flip = require('./flip'); 11 | const handleError = require('./handle_error'); 12 | const identity = require('./identity'); 13 | const keys = require('./keys'); 14 | const liftA2 = require('./lift_a2'); 15 | const liftA3 = require('./lift_a3'); 16 | const liftA4 = require('./lift_a4'); 17 | const liftA5 = require('./lift_a5'); 18 | const map = require('./map'); 19 | const maybe = require('./maybe'); 20 | const once = require('./once'); 21 | const toPairs = require('./to_pairs'); 22 | const trampoline = require('./trampoline'); 23 | const typeOf = require('./type_of'); 24 | const values = require('./values'); 25 | const zip = require('./zip'); 26 | 27 | module.exports = { 28 | ap, 29 | apply, 30 | chain, 31 | compose, 32 | curry, 33 | curryN, 34 | either, 35 | equals, 36 | every, 37 | flip, 38 | handleError, 39 | identity, 40 | keys, 41 | liftA2, 42 | liftA3, 43 | liftA4, 44 | liftA5, 45 | map, 46 | maybe, 47 | once, 48 | toPairs, 49 | trampoline, 50 | typeOf, 51 | values, 52 | zip, 53 | }; 54 | -------------------------------------------------------------------------------- /src/functions/keys.js: -------------------------------------------------------------------------------- 1 | 2 | // keys :: {String: *} -> [String] 3 | const keys = o => Object.keys(o); 4 | 5 | module.exports = keys; 6 | -------------------------------------------------------------------------------- /src/functions/lift_a2.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | const map = require('./map'); 3 | const ap = require('./ap'); 4 | 5 | // liftA2 :: (a -> b -> c) -> Functor a -> Functor b -> Functor c 6 | const liftA2 = curry((f, fa, fb) => 7 | ap(map(f, fa), fb)); 8 | 9 | module.exports = liftA2; 10 | -------------------------------------------------------------------------------- /src/functions/lift_a3.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | const compose = require('./compose'); 3 | const map = require('./map'); 4 | const ap = require('./ap'); 5 | const flip = require('./flip'); 6 | 7 | const apf = flip(ap); 8 | 9 | // liftA3 :: (a -> b -> c -> d) -> Functor a -> Functor b -> Functor c -> Functor d 10 | const liftA3 = curry((f, fa, fb, fc) => 11 | compose(apf(fc), apf(fb), map(f))(fa)); 12 | 13 | module.exports = liftA3; 14 | -------------------------------------------------------------------------------- /src/functions/lift_a4.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | const compose = require('./compose'); 3 | const map = require('./map'); 4 | const ap = require('./ap'); 5 | const flip = require('./flip'); 6 | 7 | const apf = flip(ap); 8 | 9 | // eslint-disable-next-line max-len 10 | // liftA4 :: (a -> b -> c -> d -> e) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e 11 | const liftA4 = curry((f, fa, fb, fc, fd) => 12 | compose(apf(fd), apf(fc), apf(fb), map(f))(fa)); 13 | 14 | module.exports = liftA4; 15 | -------------------------------------------------------------------------------- /src/functions/lift_a5.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | const compose = require('./compose'); 3 | const map = require('./map'); 4 | const ap = require('./ap'); 5 | const flip = require('./flip'); 6 | 7 | const apf = flip(ap); 8 | 9 | // eslint-disable-next-line max-len 10 | // liftA4 :: (a -> b -> c -> d -> e -> f) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e -> Functor f 11 | const liftA5 = curry((f, fa, fb, fc, fd, fe) => 12 | compose(apf(fe), apf(fd), apf(fc), apf(fb), map(f))(fa)); 13 | 14 | module.exports = liftA5; 15 | -------------------------------------------------------------------------------- /src/functions/map.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | 3 | 4 | // map :: (a -> b) -> F a -> F b 5 | const map = curry((f, F) => F.map(f)); 6 | 7 | module.exports = map; 8 | -------------------------------------------------------------------------------- /src/functions/maybe.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | 3 | 4 | // maybe :: b -> (a -> b) -> Maybe a -> b 5 | const map = curry((b, f, ma) => 6 | (ma.isNothing() 7 | ? b 8 | : f(ma.__value))); 9 | 10 | module.exports = map; 11 | -------------------------------------------------------------------------------- /src/functions/once.js: -------------------------------------------------------------------------------- 1 | 2 | const once = (f) => { 3 | let hasRun = false; 4 | return (...args) => { // eslint-disable-line consistent-return 5 | if (!hasRun) { 6 | hasRun = true; 7 | return f(...args); 8 | } 9 | }; 10 | }; 11 | 12 | module.exports = once; 13 | -------------------------------------------------------------------------------- /src/functions/to_pairs.js: -------------------------------------------------------------------------------- 1 | const keys = require('./keys'); 2 | const values = require('./values'); 3 | const zip = require('./zip'); 4 | 5 | 6 | // toPairs :: {String: *} -> [String, *] 7 | const toPairs = o => 8 | zip(keys(o), values(o)); 9 | 10 | module.exports = toPairs; 11 | -------------------------------------------------------------------------------- /src/functions/trampoline.js: -------------------------------------------------------------------------------- 1 | 2 | class Thunk { 3 | constructor(f) { 4 | this.f = f; 5 | } 6 | 7 | fork() { 8 | return this.f(); 9 | } 10 | } 11 | 12 | 13 | // trampoline :: (*... -> a) -> (*... -> a) 14 | const trampoline = f => (...args) => { 15 | let result = f(...args); 16 | while (result instanceof Thunk) result = result.fork(); 17 | return result; 18 | }; 19 | 20 | trampoline.thunk = f => new Thunk(f); 21 | 22 | module.exports = trampoline; 23 | -------------------------------------------------------------------------------- /src/functions/type_of.js: -------------------------------------------------------------------------------- 1 | // typeOf :: a -> String 2 | const typeOf = (x) => { 3 | if (Array.isArray(x)) return 'array'; 4 | if (x === null) return 'null'; 5 | return typeof x; 6 | }; 7 | 8 | module.exports = typeOf; 9 | -------------------------------------------------------------------------------- /src/functions/values.js: -------------------------------------------------------------------------------- 1 | const keys = require('./keys'); 2 | const map = require('./map'); 3 | 4 | // values :: {String: *} -> [*] 5 | const values = o => 6 | map(k => o[k], keys(o)); 7 | 8 | module.exports = values; 9 | -------------------------------------------------------------------------------- /src/functions/zip.js: -------------------------------------------------------------------------------- 1 | const curry = require('./curry'); 2 | const map = require('./map'); 3 | 4 | // zip :: [a] -> [b] -> [[a, b]] 5 | const zip = curry((as, bs) => 6 | map((a, i) => [a, bs[i]], as)); 7 | 8 | module.exports = zip; 9 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const functions = require('./functions'); 2 | const data = require('./data'); 3 | 4 | module.exports = { 5 | functions, 6 | data, 7 | }; 8 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | "env": { 5 | "node": true 6 | }, 7 | "rules": { 8 | "import/no-extraneous-dependencies": ["error", {"devDependencies": true}], 9 | "max-len": 0, 10 | "new-cap": 0 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /test/data/either.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const test = require('tape'); 3 | const { Either, Right, Left } = require('../../src/data'); 4 | const { compose } = require('../../src/functions'); 5 | 6 | 7 | const testError = new Error('Test error'); 8 | 9 | const double = x => x * 2; 10 | 11 | test('Either.of :: returns instance of Either and Right', (t) => { 12 | const e = Either.of(10); 13 | t.true(e instanceof Either); 14 | t.true(e instanceof Right); 15 | t.end(); 16 | }); 17 | 18 | test('Either.Right :: returns instance of Either and Right', (t) => { 19 | const e = Either.Right(10); 20 | t.true(e instanceof Either); 21 | t.true(e instanceof Right); 22 | t.end(); 23 | }); 24 | 25 | test('Either.Left :: returns instance of Either and Left', (t) => { 26 | const e = Either.Left(testError); 27 | t.true(e instanceof Either); 28 | t.true(e instanceof Left); 29 | t.end(); 30 | }); 31 | 32 | test('Either::map - Either a b ~> (b -> c) -> Either a c - Right b', (t) => { 33 | const eb = Either.of(10); 34 | const ec = eb.map(double); 35 | t.equal(ec.__value, 20); 36 | t.end(); 37 | }); 38 | 39 | test('Either::map - Either a b ~> (b -> c) -> Either a c - Left a', (t) => { 40 | const eb = Either.Left(testError); 41 | const ec = eb.map(double); 42 | t.true(ec instanceof Left); 43 | t.equal(ec.__value, testError); 44 | t.end(); 45 | }); 46 | 47 | test('Either::chain - Either a b ~> (b -> Either a c) -> Either a c - Right b', (t) => { 48 | const eb = Either.of(10); 49 | const ec = eb.chain(compose(Either.of, double)); 50 | t.true(ec instanceof Right); 51 | t.equal(ec.__value, 20); 52 | t.end(); 53 | }); 54 | 55 | test('Either::chain - Either a b ~> (b -> Either a c) -> Either a c - (Either.Left)', (t) => { 56 | const eb = Either.Left(testError); 57 | const ec = eb.chain(compose(Either.of, double)); 58 | t.true(ec instanceof Left); 59 | t.equal(ec.__value, testError); 60 | t.end(); 61 | }); 62 | 63 | test('Either::chain - Either a b ~> (b -> Either a c) -> Either a c - (chain to Either.Left)', (t) => { 64 | const eb = Either.of(10); 65 | const ec = eb.chain(() => Either.Left(testError)); 66 | t.true(ec instanceof Left); 67 | t.equal(ec.__value, testError); 68 | t.end(); 69 | }); 70 | 71 | test('Either::ap - Either a (b -> c) ~> Either a b -> Either a c - (Right (b -> c))', (t) => { 72 | const eb = Either.of(double); 73 | const ec = eb.ap(Either.of(10)); 74 | t.true(ec instanceof Right); 75 | t.equal(ec.__value, 20); 76 | t.end(); 77 | }); 78 | 79 | test('Either::ap - Either a (b -> c) ~> Either a b -> Either a c - (Left a)', (t) => { 80 | const eb = Either.Left(testError); 81 | const ec = eb.ap(Either.of(10)); 82 | t.true(ec instanceof Left); 83 | t.equal(ec.__value, testError); 84 | t.end(); 85 | }); 86 | 87 | test('Either::ap - Either a (b -> c) ~> Either a b -> Either a c - (Right (b -> c), Left a)', (t) => { 88 | const eb = Either.of(double); 89 | const ec = eb.ap(Either.Left(testError)); 90 | t.true(ec instanceof Left); 91 | t.equal(ec.__value, testError); 92 | t.end(); 93 | }); 94 | 95 | test('Either::isLeft - Either a b ~> () -> Boolean', (t) => { 96 | t.false(Either.of(1).isLeft()); 97 | t.true(Either.Left(testError).isLeft()); 98 | t.end(); 99 | }); 100 | 101 | test('Either::isRight - Either a b ~> () -> Boolean', (t) => { 102 | t.true(Either.of(1).isRight()); 103 | t.false(Either.Left(testError).isRight()); 104 | t.end(); 105 | }); 106 | -------------------------------------------------------------------------------- /test/data/future.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const sinon = require('sinon'); 3 | const { identity } = require('../../src/functions'); 4 | const { Future } = require('../../src/data'); 5 | 6 | const noop = () => {}; 7 | 8 | const resolvingAsync = (v, delay = 1, spy = noop) => ((reject, resolve) => { 9 | spy(Date.now()); 10 | setTimeout(() => { 11 | spy(Date.now()); 12 | resolve(v); 13 | }, delay); 14 | }); 15 | 16 | const rejectingAsync = (e, delay = 1) => ((reject) => { 17 | setTimeout(() => reject(e), delay); 18 | }); 19 | 20 | const testError = new Error('Test error'); 21 | 22 | 23 | test('new Future returns instance of future', (t) => { 24 | const f = new Future(); 25 | t.true(f instanceof Future); 26 | t.end(); 27 | }); 28 | 29 | test('Future::fork - executes future, receiving resolved value to onResolve handler', (t) => { 30 | const f = new Future(resolvingAsync(10)); 31 | 32 | f.fork(identity, (value) => { 33 | t.equal(value, 10); 34 | t.end(); 35 | }); 36 | }); 37 | 38 | test('Future::fork - executes future, receiving rejected value to onReject handler', (t) => { 39 | const f = new Future(rejectingAsync(testError)); 40 | 41 | f.fork((error) => { 42 | t.equal(error, testError); 43 | t.end(); 44 | }); 45 | }); 46 | 47 | test('Future::map - (Future e a).map(a -> b) -> Future e b (resolving)', (t) => { 48 | const fa = new Future(resolvingAsync(10)); 49 | const fb = fa.map(x => x * 2); 50 | 51 | t.true(fb instanceof Future); 52 | 53 | fb.fork(identity, (value) => { 54 | t.equal(value, 20); 55 | t.end(); 56 | }); 57 | }); 58 | 59 | test('Future::map - (Future e a).map(a -> b) -> Future e b (rejecting)', (t) => { 60 | const fa = new Future(rejectingAsync(testError)); 61 | const fb = fa.map(x => x * 2); 62 | 63 | t.true(fb instanceof Future); 64 | 65 | fb.fork((error) => { 66 | t.equal(error, testError); 67 | t.end(); 68 | }); 69 | }); 70 | 71 | test('Future::map - (Future e a).map(a -> b) -> Future e b (rejecting)', (t) => { 72 | const fa = new Future(resolvingAsync(10)); 73 | const fb = fa.map(() => { 74 | throw testError; 75 | }); 76 | 77 | t.true(fb instanceof Future); 78 | fb.fork((error) => { 79 | t.equal(error, testError); 80 | t.end(); 81 | }); 82 | }); 83 | 84 | test('Future::chain - (Future e a).chain(a -> Future e b) -> Future e b (resolving)', (t) => { 85 | const fa = new Future(resolvingAsync(10)); 86 | const fb = fa.chain(x => new Future(resolvingAsync(x * 2))); 87 | 88 | t.true(fb instanceof Future); 89 | 90 | fb.fork(identity, (value) => { 91 | t.equal(value, 20); 92 | t.end(); 93 | }); 94 | }); 95 | 96 | test('Future::chain - (Future e a).map(a -> Future e b) -> Future e b (1st rejecting)', (t) => { 97 | const fa = new Future(rejectingAsync(testError)); 98 | const fb = fa.chain(x => new Future(resolvingAsync(x * 2))); 99 | 100 | t.true(fb instanceof Future); 101 | 102 | fb.fork((error) => { 103 | t.equal(error, testError); 104 | t.end(); 105 | }); 106 | }); 107 | 108 | test('Future::chain - (Future e a).chain(a -> Future e b) -> Future e b (2nd rejecting)', (t) => { 109 | const fa = new Future(resolvingAsync(10)); 110 | const fb = fa.chain(() => new Future(rejectingAsync(testError))); 111 | 112 | t.true(fb instanceof Future); 113 | 114 | fb.fork((error) => { 115 | t.equal(error, testError); 116 | t.end(); 117 | }); 118 | }); 119 | 120 | test('Future::chain - (Future e a).chain(a -> Future e b) -> Future e b (rejecting)', (t) => { 121 | const fa = new Future(resolvingAsync(10)); 122 | const fb = fa.chain(() => { 123 | throw testError; 124 | }); 125 | 126 | t.true(fb instanceof Future); 127 | fb.fork((error) => { 128 | t.equal(error, testError); 129 | t.end(); 130 | }); 131 | }); 132 | 133 | test('Future::ap - (Future e (a -> b)).ap(Future e a) -> Future e b (resolving)', (t) => { 134 | const double = x => x * 2; 135 | const fa = new Future(resolvingAsync(double)); 136 | const fb = fa.ap(new Future(resolvingAsync(10))); 137 | 138 | t.true(fb instanceof Future); 139 | 140 | fb.fork(identity, (value) => { 141 | t.equal(value, 20); 142 | t.end(); 143 | }); 144 | }); 145 | 146 | test('Future::ap - (Future e (a -> b)).ap(Future e a) -> Future e b (1st rejecting)', (t) => { 147 | const fa = new Future(rejectingAsync(testError)); 148 | const fb = fa.ap(new Future(resolvingAsync(10))); 149 | 150 | t.true(fb instanceof Future); 151 | 152 | fb.fork((error) => { 153 | t.equal(error, testError); 154 | t.end(); 155 | }); 156 | }); 157 | 158 | test('Future::ap - (Future e (a -> b)).ap(Future e a) -> Future e b (2nd rejecting)', (t) => { 159 | const double = x => x * 2; 160 | const fa = new Future(resolvingAsync(double)); 161 | const fb = fa.ap(new Future(rejectingAsync(testError))); 162 | 163 | t.true(fb instanceof Future); 164 | 165 | fb.fork((error) => { 166 | t.equal(error, testError); 167 | t.end(); 168 | }); 169 | }); 170 | 171 | test('Future::ap - (Future e (a -> b)).ap(Future e a) -> Future e b (Future e (a -> b) rejecting)', (t) => { 172 | const fa = Future.of(() => { 173 | throw testError; 174 | }); 175 | const fb = fa.ap(Future.of(10)); 176 | 177 | t.true(fb instanceof Future); 178 | 179 | fb.fork((error) => { 180 | t.equal(error, testError); 181 | t.end(); 182 | }); 183 | }); 184 | 185 | test('Future::ap - (Future e (a -> b)).ap(Future e a) -> Future e b (Future e (a -> b) resolving multiple)', (t) => { 186 | const fa = Future.of(a => b => [a, b]); 187 | const fb = fa.ap(Future.of(10)).ap(Future.of(20)); 188 | 189 | t.true(fb instanceof Future); 190 | 191 | fb.fork(identity, (x) => { 192 | t.deepEqual(x, [10, 20]); 193 | t.end(); 194 | }); 195 | }); 196 | 197 | test('Future::ap - (Future e (a -> b)).ap(Future e a) -> Future e b (Future e (a -> b) rejecting async)', (t) => { 198 | const fa = new Future(resolvingAsync(a => a, 1)); 199 | const fb = fa.ap(Future.reject(testError)); 200 | 201 | t.true(fb instanceof Future); 202 | 203 | fb.fork((error) => { 204 | t.equal(error, testError); 205 | setTimeout(() => t.end(), 10); // NOTE: Add short delay to check resolveFn exits without calling resolve before test ends; 206 | }); 207 | }); 208 | 209 | test('Future::ap - (Future e (a -> b -> [a, b])).ap(Future e a).ap(Future e b) -> Future e [a, b], resolves in parallel (parallelism)', (t) => { 210 | const fa = Future.of(a => b => [a, b]); 211 | const bTimer = sinon.spy(); 212 | const cTimer = sinon.spy(); 213 | const fb = new Future(resolvingAsync(10, 20, bTimer)); 214 | const fc = new Future(resolvingAsync(20, 20, cTimer)); 215 | const fd = fa.ap(fb).ap(fc); 216 | t.plan(6); 217 | t.true(fd instanceof Future); 218 | fd.fork(identity, (x) => { 219 | t.true(bTimer.calledTwice); 220 | t.true(cTimer.calledTwice); 221 | t.true(cTimer.lastCall.args[0] > bTimer.firstCall.args[0]); 222 | t.true(bTimer.lastCall.args[0] > cTimer.firstCall.args[0]); 223 | t.deepEqual(x, [10, 20]); 224 | t.end(); 225 | }); 226 | }); 227 | 228 | test('Future.of(a) -> Future e a', (t) => { 229 | const f = Future.of(10); 230 | 231 | t.true(f instanceof Future); 232 | 233 | f.fork(identity, (value) => { 234 | t.equal(value, 10); 235 | t.end(); 236 | }); 237 | }); 238 | 239 | test('Future.reject(e) -> Future e', (t) => { 240 | const f = Future.reject(testError); 241 | 242 | t.true(f instanceof Future); 243 | 244 | f.fork((error) => { 245 | t.equal(error, testError); 246 | t.end(); 247 | }); 248 | }); 249 | -------------------------------------------------------------------------------- /test/data/maybe.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const test = require('tape'); 3 | const { Maybe, Just, Nothing } = require('../../src/data'); 4 | const { compose } = require('../../src/functions'); 5 | 6 | const double = x => x * 2; 7 | 8 | test('Maybe.of - non-nil value returns instance of Maybe and Just', (t) => { 9 | const m = Maybe.of(10); 10 | t.true(m instanceof Maybe); 11 | t.true(m instanceof Just); 12 | t.end(); 13 | }); 14 | 15 | test('Maybe.of - null returns instance of Maybe and Nothing', (t) => { 16 | const m = Maybe.of(null); 17 | t.true(m instanceof Maybe); 18 | t.true(m instanceof Nothing); 19 | t.end(); 20 | }); 21 | 22 | test('Maybe.of - undefined returns instance of Maybe and Nothing', (t) => { 23 | const m = Maybe.of(undefined); 24 | t.true(m instanceof Maybe); 25 | t.true(m instanceof Nothing); 26 | t.end(); 27 | }); 28 | 29 | test('Maybe.Nothing - returns instance of Maybe and Nothing', (t) => { 30 | const m = Maybe.Nothing; 31 | t.true(m instanceof Maybe); 32 | t.true(m instanceof Nothing); 33 | t.end(); 34 | }); 35 | 36 | test('Maybe::isNothing - Maybe a ~> () -> boolean: Just returns false', (t) => { 37 | t.false(Maybe.of(1).isNothing()); 38 | t.end(); 39 | }); 40 | 41 | test('Maybe::isNothing - Maybe a ~> () -> boolean: Nothing returns true', (t) => { 42 | t.true(Maybe.of(null).isNothing()); 43 | t.end(); 44 | }); 45 | 46 | test('Maybe::isJust - Maybe a ~> () -> boolean: Just returns true', (t) => { 47 | t.true(Maybe.of(1).isJust()); 48 | t.end(); 49 | }); 50 | 51 | test('Maybe::isJust - Maybe a ~> () -> boolean: Nothing returns false', (t) => { 52 | t.false(Maybe.of(null).isJust()); 53 | t.end(); 54 | }); 55 | 56 | test('Maybe::equals - Maybe a ~> Maybe b -> boolean - Just', (t) => { 57 | const m = Maybe.of(10); 58 | t.true(m.equals(Maybe.of(10))); 59 | t.false(m.equals(Maybe.of(2))); 60 | t.false(m.equals(Maybe.of(null))); 61 | t.end(); 62 | }); 63 | 64 | test('Maybe::equals - Maybe a ~> Maybe b -> boolean - Nothing', (t) => { 65 | const m = Maybe.of(null); 66 | t.true(m.equals(Maybe.of(null))); 67 | t.true(m.equals(Maybe.of(undefined))); 68 | t.false(m.equals(Maybe.of(1))); 69 | t.end(); 70 | }); 71 | 72 | test('Maybe::map - Maybe a ~> (a -> b) -> Maybe b - Just a', (t) => { 73 | const ma = Maybe.of(10); 74 | const mb = ma.map(double); 75 | t.equal(mb.__value, 20); 76 | t.end(); 77 | }); 78 | 79 | test('Maybe::map - Maybe a ~> (a -> b) -> Maybe b - Just a (null mapper)', (t) => { 80 | const ma = Maybe.of(10); 81 | const mb = ma.map(() => null); 82 | t.true(mb.isNothing()); 83 | t.end(); 84 | }); 85 | 86 | 87 | test('Maybe::map - Maybe a ~> (a -> b) -> Maybe b - Nothing', (t) => { 88 | const ma = Maybe.of(null); 89 | const mb = ma.map(double); 90 | t.true(mb.isNothing()); 91 | t.end(); 92 | }); 93 | 94 | test('Maybe::chain - Maybe a ~> (a -> Maybe b) -> Maybe b - Just a', (t) => { 95 | const ma = Maybe.of(10); 96 | const mb = ma.chain(compose(Maybe.of, double)); 97 | t.equal(mb.__value, 20); 98 | t.end(); 99 | }); 100 | 101 | test('Maybe::chain - Maybe a ~> (a -> Maybe b) -> Maybe b - (null mapper)', (t) => { 102 | const ma = Maybe.of(10); 103 | const mb = ma.chain(compose(Maybe.of, () => null)); 104 | t.true(mb.isNothing()); 105 | t.end(); 106 | }); 107 | 108 | test('Maybe::chain - Maybe a ~> (a -> Maybe b) -> Maybe b - Nothing', (t) => { 109 | const ma = Maybe.of(null); 110 | const mb = ma.chain(compose(Maybe.of, double)); 111 | t.true(mb.isNothing()); 112 | t.end(); 113 | }); 114 | 115 | test('Maybe::ap - Maybe (a -> b) ~> Maybe a -> Maybe b - Just (a -> b)', (t) => { 116 | const ma = Maybe.of(double); 117 | const mb = ma.ap(Maybe.of(10)); 118 | t.equal(mb.__value, 20); 119 | t.end(); 120 | }); 121 | 122 | test('Maybe::ap - Maybe (a -> b) ~> Maybe a -> Maybe b - Just (a -> null)', (t) => { 123 | const ma = Maybe.of(() => null); 124 | const mb = ma.ap(Maybe.of(10)); 125 | t.true(mb.isNothing()); 126 | t.end(); 127 | }); 128 | 129 | test('Maybe::ap - Maybe (a -> b) ~> Maybe a -> Maybe b - Just (a -> b)', (t) => { 130 | const ma = Maybe.of(() => null); 131 | const mb = ma.ap(Maybe.of(null)); 132 | t.true(mb.isNothing()); 133 | t.end(); 134 | }); 135 | 136 | test('Maybe::ap - Maybe (a -> b) ~> Maybe a -> Maybe b - Nothing', (t) => { 137 | const ma = Maybe.of(null); 138 | const mb = ma.ap(Maybe.of(1)); 139 | t.true(mb.isNothing()); 140 | t.end(); 141 | }); 142 | -------------------------------------------------------------------------------- /test/functions/ap.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const test = require('tape'); 3 | const { ap } = require('../../src/functions'); 4 | const { Maybe } = require('../../src/data'); 5 | 6 | const double = a => a * 2; 7 | 8 | test('ap :: Functor (a -> b) -> Functor a -> Functor b', (t) => { 9 | t.equal(ap(Maybe.of(double), Maybe.of(10)).__value, 20); 10 | t.end(); 11 | }); 12 | 13 | 14 | test('ap :: Functor (a -> b) -> Functor a -> Functor b', (t) => { 15 | t.true(ap(Maybe.Nothing, Maybe.of(10)).isNothing()); 16 | t.end(); 17 | }); 18 | 19 | 20 | test('ap :: Functor (a -> b) -> Functor a -> Functor b', (t) => { 21 | t.true(ap(Maybe.of(double), Maybe.Nothing).isNothing()); 22 | t.end(); 23 | }); 24 | -------------------------------------------------------------------------------- /test/functions/apply.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const sinon = require('sinon'); 3 | const { apply, identity } = require('../../src/functions'); 4 | 5 | test('apply :: (a... -> b) -> [a...] -> b - applies args to function', (t) => { 6 | const spy = sinon.spy(); 7 | apply(spy, ['a', 'b', 'c']); 8 | t.plan(2); 9 | t.true(spy.calledOnce); 10 | t.true(spy.calledWithExactly('a', 'b', 'c')); 11 | t.end(); 12 | }); 13 | 14 | test('apply :: (a... -> b) -> [a...] -> b - returns result of apply args to function', (t) => { 15 | t.deepEqual(apply(identity, [[1, 2, 3]]), [1, 2, 3]); 16 | t.end(); 17 | }); 18 | -------------------------------------------------------------------------------- /test/functions/chain.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const test = require('tape'); 3 | const { chain, identity, compose } = require('../../src/functions'); 4 | const { Either, Future, Maybe } = require('../../src/data'); 5 | 6 | 7 | const double = x => x * 2; 8 | 9 | 10 | test('chain :: (a -> M b) -> M a -> M b', (t) => { 11 | t.equal(chain(compose(Maybe.of, double), Maybe.of(10)).__value, 20); 12 | t.end(); 13 | }); 14 | 15 | test('chain :: (a -> M b) -> M a -> M b', (t) => { 16 | t.true(chain(compose(Maybe.of, double), Maybe.Nothing).isNothing()); 17 | t.end(); 18 | }); 19 | 20 | test('chain :: (a -> M b) -> M a -> M b', (t) => { 21 | t.equal(chain(compose(Either.of, double), Either.of(10)).__value, 20); 22 | t.end(); 23 | }); 24 | 25 | test('chain :: (a -> M b) -> M a -> M b', (t) => { 26 | t.equal(chain(compose(Either.of, double), Either.Left(10)).__value, 10); 27 | t.end(); 28 | }); 29 | 30 | test('chain :: (a -> M b) -> M a -> M b', (t) => { 31 | chain(compose(Future.of, double), Future.of(10)).fork(identity, (v) => { 32 | t.equal(v, 20); 33 | t.end(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/functions/compose.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { compose } = require('../../src/functions'); 3 | 4 | const add = a => b => a + b; 5 | const mult = a => b => a * b; 6 | 7 | test('compose :: combines two functions, applying right to left', (t) => { 8 | t.equal(compose(add(2), mult(3))(5), 17); 9 | t.end(); 10 | }); 11 | 12 | test('compose :: function order matters', (t) => { 13 | t.not( 14 | compose(add(2), mult(3))(5), 15 | compose(mult(3), add(2))(5) 16 | ); 17 | t.end(); 18 | }); 19 | 20 | test('compose :: combines more than two functions, applying right to left', (t) => { 21 | t.equal( 22 | compose(add(2), mult(3), add(2), add(3))(5), 23 | 32 24 | ); 25 | t.end(); 26 | }); 27 | 28 | test('compose :: works with a single function', (t) => { 29 | t.equal(compose(add(2))(5), 7); 30 | t.end(); 31 | }); 32 | -------------------------------------------------------------------------------- /test/functions/curry.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { curry } = require('../../src/functions'); 3 | 4 | const addA2 = (a, b) => a + b; 5 | const addA7 = (a, b, c, d, e, f, g) => a + b + c + d + e + f + g; 6 | 7 | test('curry :: ((a, b, ..., f) => g) => a => b => ... f => g - returns function', (t) => { 8 | const curried = curry(addA2); 9 | t.true(typeof curried === 'function'); 10 | t.end(); 11 | }); 12 | 13 | test('curry :: ((a, b, ..., f) => g) => a => b => ... f => g - return function which partially applies until all args received', (t) => { 14 | const curried = curry(addA2); 15 | t.true(typeof curried(1) === 'function'); 16 | t.end(); 17 | }); 18 | 19 | test('curry :: ((a, b, ..., f) => g) => a => b => ... f => g - return function which partially applies until all args received', (t) => { 20 | const curried = curry(addA2); 21 | t.equal(curried(1, 2), curried(1)(2)); 22 | t.end(); 23 | }); 24 | 25 | test('curry :: ((a, b, ..., f) => g) => a => b => ... f => g - use fallback __curry for arity > 6', (t) => { 26 | const curried = curry(addA7); 27 | t.equal(curried(1, 2, 3, 4, 5, 6, 7), curried(1)(2)(3)(4)(5)(6)(7)); 28 | t.end(); 29 | }); 30 | -------------------------------------------------------------------------------- /test/functions/curryN.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { curryN } = require('../../src/functions'); 3 | 4 | const addA2 = (a, b) => a + b; 5 | const addA3 = (a, b, c) => a + b + c; 6 | const addA4 = (a, b, c, d) => a + b + c + d; 7 | const addA5 = (a, b, c, d, e) => a + b + c + d + e; 8 | const addA6 = (a, b, c, d, e, f) => a + b + c + d + e + f; 9 | 10 | test('curryN :: ((a, b, ..., f) => g, n) => a => b => ... f => g - returns function', (t) => { 11 | const curried = curryN(addA2, 2); 12 | t.true(typeof curried === 'function'); 13 | t.end(); 14 | }); 15 | 16 | test('curryN :: ((a, b, ..., f) => g, n) => a => b => ... f => g - return function which partially applies until all args received', (t) => { 17 | const curried = curryN(addA2, 2); 18 | t.true(typeof curried(1) === 'function'); 19 | t.end(); 20 | }); 21 | 22 | test('curryN :: ((a, b, ..., f) => g, n) => a => b => ... f => g - return function which partially applies until all args received', (t) => { 23 | const curried = curryN(2, addA2); 24 | t.equal(curried()(1, 2), 3); 25 | t.equal(curried(1, 2), 3); 26 | t.equal(curried(1)(2), 3); 27 | t.end(); 28 | }); 29 | 30 | test('curryN :: ((a, b, ..., f) => g, n) => a => b => ... f => g - return function which partially applies until all args received', (t) => { 31 | const curried = curryN(3, addA3); 32 | t.equal(curried()(1, 2, 3), 6); 33 | t.equal(curried(1, 2, 3), 6); 34 | t.equal(curried(1)(2, 3), 6); 35 | t.equal(curried(1, 2)(3), 6); 36 | t.end(); 37 | }); 38 | 39 | test('curryN :: ((a, b, ..., f) => g, n) => a => b => ... f => g - return function which partially applies until all args received', (t) => { 40 | const curried = curryN(4, addA4); 41 | t.equal(curried()(1, 2, 3, 4), 10); 42 | t.equal(curried(1, 2, 3, 4), 10); 43 | t.equal(curried(1)(2, 3, 4), 10); 44 | t.equal(curried(1, 2, 3)(4), 10); 45 | t.equal(curried(1, 2)(3, 4), 10); 46 | t.end(); 47 | }); 48 | 49 | test('curryN :: ((a, b, ..., f) => g, n) => a => b => ... f => g - return function which partially applies until all args received', (t) => { 50 | const curried = curryN(5, addA5); 51 | t.equal(curried()(1, 2, 3, 4, 5), 15); 52 | t.equal(curried(1, 2, 3, 4, 5), 15); 53 | t.equal(curried(1)(2, 3, 4, 5), 15); 54 | t.equal(curried(1, 2)(3, 4, 5), 15); 55 | t.equal(curried(1, 2, 3)(4, 5), 15); 56 | t.equal(curried(1, 2, 3, 4)(5), 15); 57 | t.end(); 58 | }); 59 | 60 | test('curryN :: ((a, b, ..., f) => g, n) => a => b => ... f => g - return function which partially applies until all args received', (t) => { 61 | const curried = curryN(6, addA6); 62 | t.equal(curried()(1, 2, 3, 4, 5, 6), 21); 63 | t.equal(curried(1, 2, 3, 4, 5, 6), 21); 64 | t.equal(curried(1)(2, 3, 4, 5, 6), 21); 65 | t.equal(curried(1, 2)(3, 4, 5, 6), 21); 66 | t.equal(curried(1, 2, 3)(4, 5, 6), 21); 67 | t.equal(curried(1, 2, 3, 4)(5, 6), 21); 68 | t.equal(curried(1, 2, 3, 4, 5)(6), 21); 69 | t.end(); 70 | }); 71 | 72 | test('curryN :: ((a, b, ..., f) => g, n) => a => b => ... f => g - return function which partially applies until all args received', (t) => { 73 | const curried = curryN(1, a => a + 1); 74 | t.equal(curried()(1), 2); 75 | t.equal(curried(1), 2); 76 | t.end(); 77 | }); 78 | -------------------------------------------------------------------------------- /test/functions/either.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { either } = require('../../src/functions'); 3 | const { Either } = require('../../src/data'); 4 | 5 | const left = () => 0; 6 | const double = b => b * 2; 7 | const doubleOrZero = either(left, double); 8 | 9 | test('either :: (a -> c) -> (b -> c) -> Either a b -> c', (t) => { 10 | t.equal(doubleOrZero(Either.of(2)), 4); 11 | t.end(); 12 | }); 13 | 14 | test('either :: (a -> c) -> (b -> c) -> Either a b -> c', (t) => { 15 | t.equal(doubleOrZero(Either.Left(1)), 0); 16 | t.end(); 17 | }); 18 | -------------------------------------------------------------------------------- /test/functions/equals.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { equals } = require('../../src/functions'); 3 | 4 | 5 | test('equals :: a -> b -> Boolean - compares booleans', (t) => { 6 | t.true(equals(true, true)); 7 | t.true(equals(false, false)); 8 | t.false(equals(false, true)); 9 | t.end(); 10 | }); 11 | 12 | test('equals :: a -> b -> Boolean - compares strings', (t) => { 13 | t.true(equals('abc', 'abc')); 14 | t.false(equals('cba', 'bca')); 15 | t.end(); 16 | }); 17 | 18 | test('equals :: a -> b -> Boolean - compares numbers', (t) => { 19 | t.true(equals(1, 1)); 20 | t.false(equals(1, 2)); 21 | t.end(); 22 | }); 23 | 24 | test('equals :: a -> b -> Boolean - compares null', (t) => { 25 | t.true(equals(null, null)); 26 | t.false(equals(null, undefined)); 27 | t.end(); 28 | }); 29 | 30 | test('equals :: a -> b -> Boolean - no type coercion', (t) => { 31 | t.false(equals(1, '1')); 32 | t.false(equals(1, true)); 33 | t.end(); 34 | }); 35 | 36 | test('equals :: a -> b -> Boolean - compares arrays', (t) => { 37 | t.true(equals([1, 2, 3], [1, 2, 3])); 38 | t.false(equals([1, 2, 3], [1, 2, 4])); 39 | t.end(); 40 | }); 41 | 42 | test('equals :: a -> b -> Boolean - compares objects', (t) => { 43 | t.true(equals({ a: 1, b: '2', c: false }, { a: 1, b: '2', c: false })); 44 | t.false(equals({ a: 1, b: '2', c: true }, { a: 1, b: '2', c: false })); 45 | t.end(); 46 | }); 47 | 48 | test('equals :: a -> b -> Boolean - compares objects', (t) => { 49 | t.true( 50 | equals({ a: 1, b: '2', c: [1, 2, { a: 'f', b: [1, 2, 3] }] }, 51 | { a: 1, b: '2', c: [1, 2, { a: 'f', b: [1, 2, 3] }] })); 52 | t.false( 53 | equals({ a: 1, b: '2', c: [1, 2, { a: 'f', b: [1, 2, 3] }] }, 54 | { a: 1, b: '2', c: [1, 2, { a: 'f', b: [1, 2, 3, 4] }] }) 55 | ); 56 | t.end(); 57 | }); 58 | -------------------------------------------------------------------------------- /test/functions/every.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { every } = require('../../src/functions'); 3 | 4 | const isEven = x => x % 2 === 0; 5 | const evenNos = [2, 4, 6, 8, 12, 16]; 6 | const mixedNos = [2, 4, 6, 7, 8]; 7 | 8 | test('every :: (a -> Boolean) -> [a] -> Boolean', (t) => { 9 | const areEven = every(isEven); 10 | t.plan(2); 11 | t.true(areEven(evenNos)); 12 | t.false(areEven(mixedNos)); 13 | }); 14 | -------------------------------------------------------------------------------- /test/functions/flip.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { flip } = require('../../src/functions'); 3 | 4 | const divide = (a, b) => a / b; 5 | const divide3 = (a, b, c) => a / b / c; 6 | const divide7 = (a, b, c, d, e, f, g) => a / b / c / d / e / f / g; 7 | 8 | test('flip :: (a, ... b -> c) -> (b..., a -> c)', (t) => { 9 | t.equal(flip(divide)(2, 3), 3 / 2); 10 | t.end(); 11 | }); 12 | 13 | test('flip :: (a, ... b -> c) -> (b..., a -> c)', (t) => { 14 | t.equal(flip(divide)(2)(3), 3 / 2); 15 | t.end(); 16 | }); 17 | 18 | test('flip :: (a, ... b -> c) -> (b..., a -> c)', (t) => { 19 | t.equal(flip(divide3)(2, 3, 4), 4 / 3 / 2); 20 | t.end(); 21 | }); 22 | 23 | test('flip :: (a, ... b -> c) -> (b..., a -> c)', (t) => { 24 | t.equal(flip(divide3)(2, 3)(4), 4 / 3 / 2); 25 | t.end(); 26 | }); 27 | 28 | test('flip :: (a, ... b -> c) -> (b..., a -> c) - arity > 6', (t) => { 29 | t.equal(flip(divide7)(1, 2, 3, 4, 5, 6, 7), 7 / 6 / 5 / 4 / 3 / 2 / 1); 30 | t.end(); 31 | }); 32 | -------------------------------------------------------------------------------- /test/functions/handle_error.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const sinon = require('sinon'); 3 | const { handleError } = require('../../src/functions'); 4 | 5 | const testError = new Error('Test error'); 6 | 7 | const errorFn = (x) => { 8 | if (x > 10) throw testError; 9 | return x; 10 | }; 11 | 12 | 13 | test('handleError :: (e -> c) -> (a -> b) -> a -> (b | c) - returns b if no error', (t) => { 14 | const onError = sinon.spy(); 15 | const res = handleError(onError, errorFn, 10); 16 | t.plan(2); 17 | t.equal(res, 10); 18 | t.false(onError.called); 19 | t.end(); 20 | }); 21 | 22 | test('handleError :: (e -> c) -> (a -> b) -> a -> (b | c) - calls onError handler if error', (t) => { 23 | const onError = sinon.spy(); 24 | handleError(onError, errorFn, 11); 25 | t.plan(3); 26 | t.true(onError.called); 27 | t.true(onError.calledOnce); 28 | t.true(onError.calledWith(testError)); 29 | t.end(); 30 | }); 31 | 32 | test('handleError :: (e -> c) -> (a -> b) -> a -> (b | c) - calls onError handler if error', (t) => { 33 | const onError = e => ['error handled', e]; 34 | const res = handleError(onError, errorFn, 11); 35 | t.deepEqual(res, ['error handled', testError]); 36 | t.end(); 37 | }); 38 | 39 | test('handleError :: (e -> c) -> (a -> b) -> a -> (b | c) - arguments curried', (t) => { 40 | const onError = e => ['error handled', e]; 41 | const f = handleError(onError, errorFn); 42 | t.deepEqual(f(11), ['error handled', testError]); 43 | t.end(); 44 | }); 45 | -------------------------------------------------------------------------------- /test/functions/identity.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { identity } = require('../../src/functions'); 3 | 4 | 5 | test('identity :: x -> x', (t) => { 6 | const o = {}; 7 | t.equal(identity(o), o); 8 | t.end(); 9 | }); 10 | -------------------------------------------------------------------------------- /test/functions/keys.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { keys } = require('../../src/functions'); 3 | 4 | const o = { 5 | a: 1, 6 | b: '2', 7 | c: [1, 2, 3], 8 | d: { a: 1, b: 2, c: 3 }, 9 | e: false, 10 | f: null, 11 | 1: 3, 12 | }; 13 | 14 | 15 | test('keys :: {String: *} -> [String]', (t) => { 16 | t.deepEqual( 17 | keys(o).sort(), 18 | ['a', 'b', 'c', 'd', 'e', 'f', '1'].sort()); 19 | t.end(); 20 | }); 21 | -------------------------------------------------------------------------------- /test/functions/lift_a2.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const test = require('tape'); 3 | const { liftA2, identity } = require('../../src/functions'); 4 | const { Maybe, Either, Future } = require('../../src/data'); 5 | 6 | const add = a => b => a + b; 7 | const testError = new Error('Test error'); 8 | 9 | test('liftA2 :: (a -> b -> c) -> F a -> F b -> F c', (t) => { 10 | t.equal(liftA2(add, Maybe.of(2), Maybe.of(3)).__value, 5); 11 | t.end(); 12 | }); 13 | 14 | test('liftA2 :: (a -> b -> c) -> F a -> F b -> F c', (t) => { 15 | t.true(liftA2(add, Maybe.Nothing, Maybe.of(3)).isNothing()); 16 | t.end(); 17 | }); 18 | 19 | test('liftA2 :: (a -> b -> c) -> F a -> F b -> F c', (t) => { 20 | t.equal(liftA2(add, Either.of(2), Either.of(3)).__value, 5); 21 | t.end(); 22 | }); 23 | 24 | test('liftA2 :: (a -> b -> c) -> F a -> F b -> F c', (t) => { 25 | t.equal(liftA2(add, Either.of(2), Either.Left(testError)).__value, testError); 26 | t.end(); 27 | }); 28 | 29 | test('liftA2 :: (a -> b -> c) -> F a -> F b -> F c', (t) => { 30 | liftA2(add, Future.of(2), Future.of(3)).fork(identity, (v) => { 31 | t.equal(v, 5); 32 | t.end(); 33 | }); 34 | }); 35 | 36 | test('liftA2 :: (a -> b -> c) -> F a -> F b -> F c', (t) => { 37 | liftA2(add, Future.reject(testError), Future.of(3)).fork((error) => { 38 | t.equal(error, testError); 39 | t.end(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/functions/lift_a3.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const test = require('tape'); 3 | const { liftA3, identity } = require('../../src/functions'); 4 | const { Maybe, Either, Future } = require('../../src/data'); 5 | 6 | const add = a => b => c => a + b + c; 7 | const testError = new Error('Test error'); 8 | 9 | test('liftA3 :: (a -> b -> c -> d) -> Functor a -> Functor b -> Functor c', (t) => { 10 | t.equal(liftA3(add, Maybe.of(2), Maybe.of(3), Maybe.of(5)).__value, 10); 11 | t.end(); 12 | }); 13 | 14 | test('liftA3 :: (a -> b -> c -> d) -> Functor a -> Functor b -> Functor c', (t) => { 15 | t.true(liftA3(add, Maybe.Nothing, Maybe.of(3), Maybe.of(5)).isNothing()); 16 | t.end(); 17 | }); 18 | 19 | test('liftA3 :: (a -> b -> c -> d) -> Functor a -> Functor b -> Functor c', (t) => { 20 | t.equal(liftA3(add, Either.of(2), Either.of(3), Either.of(5)).__value, 10); 21 | t.end(); 22 | }); 23 | 24 | test('liftA3 :: (a -> b -> c -> d) -> Functor a -> Functor b -> Functor c', (t) => { 25 | t.equal(liftA3(add, Either.of(2), Either.Left(testError), Either.of(5)).__value, testError); 26 | t.end(); 27 | }); 28 | 29 | test('liftA3 :: (a -> b -> c -> d) -> Functor a -> Functor b -> Functor c', (t) => { 30 | liftA3(add, Future.of(2), Future.of(3), Future.of(5)).fork(identity, (v) => { 31 | t.equal(v, 10); 32 | t.end(); 33 | }); 34 | }); 35 | 36 | test('liftA3 :: (a -> b -> c -> d) -> Functor a -> Functor b -> Functor c', (t) => { 37 | liftA3(add, Future.reject(testError), Future.of(3), Future.of(5)).fork((error) => { 38 | t.equal(error, testError); 39 | t.end(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/functions/lift_a4.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const test = require('tape'); 3 | const { liftA4, identity } = require('../../src/functions'); 4 | const { Maybe, Either, Future } = require('../../src/data'); 5 | 6 | const add = a => b => c => d => a + b + c + d; 7 | const testError = new Error('Test error'); 8 | 9 | test('liftA4 :: (a -> b -> c -> d -> e) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e', (t) => { 10 | t.equal(liftA4(add, Maybe.of(2), Maybe.of(3), Maybe.of(5), Maybe.of(6)).__value, 16); 11 | t.end(); 12 | }); 13 | 14 | test('liftA4 :: (a -> b -> c -> d -> e) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e', (t) => { 15 | t.true(liftA4(add, Maybe.Nothing, Maybe.of(3), Maybe.of(5), Maybe.of(6)).isNothing()); 16 | t.end(); 17 | }); 18 | 19 | test('liftA4 :: (a -> b -> c -> d -> e) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e', (t) => { 20 | t.equal(liftA4(add, Either.of(2), Either.of(3), Either.of(5), Either.of(6)).__value, 16); 21 | t.end(); 22 | }); 23 | 24 | test('liftA4 :: (a -> b -> c -> d -> e) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e', (t) => { 25 | t.equal(liftA4(add, Either.of(2), Either.Left(testError), Either.of(5), Either.of(6)).__value, testError); 26 | t.end(); 27 | }); 28 | 29 | test('liftA4 :: (a -> b -> c -> d -> e) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e', (t) => { 30 | liftA4(add, Future.of(2), Future.of(3), Future.of(5), Future.of(6)).fork(identity, (v) => { 31 | t.equal(v, 16); 32 | t.end(); 33 | }); 34 | }); 35 | 36 | test('liftA4 :: (a -> b -> c -> d -> e) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e', (t) => { 37 | liftA4(add, Future.reject(testError), Future.of(3), Future.of(5), Future.of(6)).fork((error) => { 38 | t.equal(error, testError); 39 | t.end(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/functions/lift_a5.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const test = require('tape'); 3 | const { liftA5, identity } = require('../../src/functions'); 4 | const { Maybe, Either, Future } = require('../../src/data'); 5 | 6 | const add = a => b => c => d => e => a + b + c + d + e; 7 | const testError = new Error('Test error'); 8 | 9 | test('liftA5 :: (a -> b -> c -> d -> e -> f) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e -> Functor f', (t) => { 10 | t.equal(liftA5(add, Maybe.of(2), Maybe.of(3), Maybe.of(5), Maybe.of(6), Maybe.of(7)).__value, 23); 11 | t.end(); 12 | }); 13 | 14 | test('liftA5 :: (a -> b -> c -> d -> e -> f) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e -> Functor f', (t) => { 15 | t.true(liftA5(add, Maybe.Nothing, Maybe.of(3), Maybe.of(5), Maybe.of(6), Maybe.of(7)).isNothing()); 16 | t.end(); 17 | }); 18 | 19 | test('liftA5 :: (a -> b -> c -> d -> e -> f) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e -> Functor f', (t) => { 20 | t.equal(liftA5(add, Either.of(2), Either.of(3), Either.of(5), Either.of(6), Either.of(7)).__value, 23); 21 | t.end(); 22 | }); 23 | 24 | test('liftA5 :: (a -> b -> c -> d -> e -> f) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e -> Functor f', (t) => { 25 | t.equal(liftA5(add, Either.of(2), Either.Left(testError), Either.of(5), Either.of(6), Either.of(7)).__value, testError); 26 | t.end(); 27 | }); 28 | 29 | test('liftA5 :: (a -> b -> c -> d -> e -> f) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e -> Functor f', (t) => { 30 | liftA5(add, Future.of(2), Future.of(3), Future.of(5), Future.of(6), Future.of(7)).fork(identity, (v) => { 31 | t.equal(v, 23); 32 | t.end(); 33 | }); 34 | }); 35 | 36 | test('liftA5 :: (a -> b -> c -> d -> e -> f) -> Functor a -> Functor b -> Functor c -> Functor d -> Functor e -> Functor f', (t) => { 37 | liftA5(add, Future.reject(testError), Future.of(3), Future.of(5), Future.of(6), Future.of(7)).fork((error) => { 38 | t.equal(error, testError); 39 | t.end(); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/functions/map.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const test = require('tape'); 3 | const { map, identity } = require('../../src/functions'); 4 | const { Either, Future, Maybe } = require('../../src/data'); 5 | 6 | 7 | const double = x => x * 2; 8 | const mapDouble = map(double); 9 | 10 | test('map :: (a -> b) -> F a -> F b', (t) => { 11 | t.deepEqual(mapDouble([1, 2, 3]), [2, 4, 6]); 12 | t.end(); 13 | }); 14 | 15 | test('map :: (a -> b) -> F a -> F b', (t) => { 16 | t.equal(mapDouble(Maybe.of(10)).__value, 20); 17 | t.end(); 18 | }); 19 | 20 | test('map :: (a -> b) -> F a -> F b', (t) => { 21 | t.true(mapDouble(Maybe.Nothing).isNothing()); 22 | t.end(); 23 | }); 24 | 25 | test('map :: (a -> b) -> F a -> F b', (t) => { 26 | t.equal(mapDouble(Either.of(10)).__value, 20); 27 | t.end(); 28 | }); 29 | 30 | test('map :: (a -> b) -> F a -> F b', (t) => { 31 | t.equal(mapDouble(Either.Left(10)).__value, 10); 32 | t.end(); 33 | }); 34 | 35 | test('map :: (a -> b) -> F a -> F b', (t) => { 36 | mapDouble(Future.of(10)).fork(identity, (v) => { 37 | t.equal(v, 20); 38 | t.end(); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/functions/maybe.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { maybe } = require('../../src/functions'); 3 | const { Maybe } = require('../../src/data'); 4 | 5 | const double = x => x * 2; 6 | const doubleOrZero = maybe(0, double); 7 | 8 | test('maybe :: b -> (a -> b) -> Maybe a -> b', (t) => { 9 | t.equal(doubleOrZero(Maybe.of(2)), 4); 10 | t.end(); 11 | }); 12 | 13 | test('maybe :: b -> (a -> b) -> Maybe a -> b', (t) => { 14 | t.equal(doubleOrZero(Maybe.Nothing), 0); 15 | t.end(); 16 | }); 17 | -------------------------------------------------------------------------------- /test/functions/once.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const sinon = require('sinon'); 3 | const { once } = require('../../src/functions'); 4 | 5 | test('once :: (a... -> b) -> (a... -> b) - returns a function', (t) => { 6 | const f = a => a; 7 | t.true(typeof once(f) === 'function'); 8 | t.end(); 9 | }); 10 | 11 | test('once :: (a... -> b) -> (a... -> b) - first call runs inner function', (t) => { 12 | const f = a => a; 13 | const onceF = once(f); 14 | t.equal(onceF(10), 10); 15 | t.equal(onceF(10), undefined); 16 | t.end(); 17 | }); 18 | 19 | test('once :: (a... -> b) -> (a... -> b) - subsequent calls return undefined', (t) => { 20 | const f = a => a; 21 | const onceF = once(f); 22 | t.equal(onceF(10), 10); 23 | t.equal(onceF(10), undefined); 24 | t.equal(onceF(10), undefined); 25 | t.end(); 26 | }); 27 | 28 | test('once :: (a... -> b) -> (a... -> b) - runs inner function once only', (t) => { 29 | const f = sinon.spy(); 30 | const onceF = once(f); 31 | Array.from({ length: 10 }).forEach(onceF); 32 | t.true(f.calledOnce); 33 | t.end(); 34 | }); 35 | -------------------------------------------------------------------------------- /test/functions/private_curry.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | const test = require('tape'); 3 | const __curry = require('../../src/functions/__curry'); 4 | 5 | const addA2 = (a, b) => a + b; 6 | 7 | test('__curry :: ((a, b, ..., f) => g) => a => b => ... f => g - returns function', (t) => { 8 | const curried = __curry(addA2); 9 | t.true(typeof curried === 'function'); 10 | t.end(); 11 | }); 12 | 13 | test('__curry :: ((a, b, ..., f) => g) => a => b => ... f => g - return function which partially applies until all args received', (t) => { 14 | const curried = __curry(addA2); 15 | t.true(typeof curried(1) === 'function'); 16 | t.end(); 17 | }); 18 | 19 | test('__curry :: ((a, b, ..., f) => g) => a => b => ... f => g - return function which partially applies until all args received', (t) => { 20 | const curried = __curry(addA2); 21 | t.equal(curried(1, 2), curried(1)(2)); 22 | t.end(); 23 | }); 24 | -------------------------------------------------------------------------------- /test/functions/to_pairs.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { toPairs } = require('../../src/functions'); 3 | 4 | const o = { 5 | a: 1, 6 | b: '2', 7 | c: [1, 2, 3], 8 | d: { a: 1, b: 2, c: 3 }, 9 | }; 10 | 11 | 12 | test('toPairs :: {String: *} -> [[String, *]]', (t) => { 13 | t.deepEqual( 14 | toPairs(o), 15 | [ 16 | ['a', 1], 17 | ['b', '2'], 18 | ['c', [1, 2, 3]], 19 | ['d', { a: 1, b: 2, c: 3 }]]); 20 | t.end(); 21 | }); 22 | 23 | test('toPairs :: {String: *} -> [[String, *]]', (t) => { 24 | t.deepEqual(toPairs([]), []); 25 | t.end(); 26 | }); 27 | -------------------------------------------------------------------------------- /test/functions/trampoline.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { trampoline } = require('../../src/functions'); 3 | 4 | const add = (a, b) => a + b; 5 | 6 | const pureFactorial = n => 7 | (n > 0 8 | ? n * pureFactorial(n - 1) 9 | : 1); 10 | 11 | const tailCallFactorial = (n, acc = 1) => 12 | (n > 0 13 | ? tailCallFactorial(n - 1, acc * n) 14 | : acc); 15 | 16 | const thunkFactorial = (p, acc = 1) => 17 | (p > 0 18 | ? trampoline.thunk(() => thunkFactorial(p - 1, acc * p)) 19 | : acc); 20 | 21 | const trampolineFactorial = trampoline(thunkFactorial); 22 | 23 | const factorial50 = 30414093201713378043612608166064768844377641568960512000000000000; 24 | 25 | const zero = () => null; 26 | const succ = prev => () => prev; 27 | 28 | const one = succ(zero); 29 | const two = succ(one); 30 | const three = succ(two); 31 | 32 | const natToInt = nat => 33 | (nat === zero 34 | ? 0 35 | : 1 + natToInt(nat())); 36 | 37 | const thunkNatToInt = (nat, acc = 0) => 38 | (nat === zero 39 | ? acc 40 | : trampoline.thunk(() => thunkNatToInt(nat(), acc + 1))); 41 | 42 | const trampolineNatToInt = trampoline(thunkNatToInt); 43 | 44 | const intToNat = (n) => { 45 | let res = zero; 46 | for (let i = 0; i < n; i += 1) res = succ(res); 47 | return res; 48 | }; 49 | 50 | const intToNatRecursive = n => 51 | (n === 0 52 | ? zero 53 | : succ(intToNatRecursive(n - 1))); 54 | 55 | const thunkIntToNat = (n, acc = zero) => 56 | (n === 0 57 | ? acc 58 | : trampoline.thunk(() => thunkIntToNat(n - 1, succ(acc)))); 59 | 60 | const trampolineIntToNat = trampoline(thunkIntToNat); 61 | 62 | 63 | const nat20000 = intToNat(20000); 64 | const nat50000 = intToNat(50000); 65 | 66 | 67 | test('trampoline :: (*... -> a) -> (*... -> a) - simple function', (t) => { 68 | t.deepEqual(trampoline(add)(1, 2), 3); 69 | t.end(); 70 | }); 71 | 72 | test('pureFactorial works', (t) => { 73 | t.equal(pureFactorial(2), 2); // Check small numbers 74 | t.equal(pureFactorial(5), 120); 75 | t.equal(pureFactorial(50), factorial50); // Check large numbers still work 76 | t.equal(pureFactorial(200), Infinity); // Check function works (although ouside of Js numbers) 77 | t.throws(() => pureFactorial(20000), /Maximum call stack size exceeded/); // Check stack overflow size 78 | t.end(); 79 | }); 80 | 81 | test('tailCallFactorial works until stack overflows', (t) => { 82 | t.equal(tailCallFactorial(2), 2); 83 | t.equal(tailCallFactorial(5), 120); 84 | t.equal(tailCallFactorial(50), factorial50); 85 | t.equal(tailCallFactorial(200), Infinity); 86 | t.throws(() => tailCallFactorial(20000), /Maximum call stack size exceeded/); 87 | t.end(); 88 | }); 89 | 90 | test('trampolineFactorial', (t) => { 91 | t.equal(trampolineFactorial(2), 2); 92 | t.equal(trampolineFactorial(5), 120); 93 | t.equal(trampolineFactorial(50), factorial50); 94 | t.equal(trampolineFactorial(200), Infinity); 95 | t.equal(trampolineFactorial(20000), Infinity); 96 | t.end(); 97 | }); 98 | 99 | test('natToInt turns natural to integer (recursively)', (t) => { 100 | t.equal(natToInt(three), 3); 101 | t.equal(natToInt(intToNat(20)), 20); 102 | t.end(); 103 | }); 104 | 105 | test('natToInt throws stack overflow for large numbers', (t) => { 106 | t.throws(() => natToInt(nat20000), /Maximum call stack size exceeded/); 107 | t.end(); 108 | }); 109 | 110 | test('trampolineNatToInt works even for large numbers', (t) => { 111 | t.equal(trampolineNatToInt(nat20000), 20000); 112 | t.equal(trampolineNatToInt(nat50000), 50000); 113 | t.end(); 114 | }); 115 | 116 | 117 | test('intToNatRecursive turns int to nat recursively', (t) => { 118 | t.equal(trampolineNatToInt(intToNatRecursive(100)), 100); 119 | t.end(); 120 | }); 121 | 122 | test('intToNatRecursive throws stack overflow for large numbers', (t) => { 123 | t.throws(() => trampolineNatToInt(intToNatRecursive(20000)), /Maximum call stack size exceeded/); 124 | t.end(); 125 | }); 126 | 127 | test('intToNatRecursive throws stack overflow for large numbers', (t) => { 128 | t.equal(trampolineNatToInt(trampolineIntToNat(20000)), 20000); 129 | t.end(); 130 | }); 131 | -------------------------------------------------------------------------------- /test/functions/type_of.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { typeOf } = require('../../src/functions'); 3 | 4 | test('typeOf :: * -> String', (t) => { 5 | t.equal(typeOf('abc'), 'string'); 6 | t.end(); 7 | }); 8 | 9 | test('typeOf :: * -> String', (t) => { 10 | t.equal(typeOf(1), 'number'); 11 | t.end(); 12 | }); 13 | 14 | test('typeOf :: * -> String', (t) => { 15 | t.equal(typeOf(0x01), 'number'); 16 | t.end(); 17 | }); 18 | 19 | test('typeOf :: * -> String', (t) => { 20 | t.equal(typeOf(1e3), 'number'); 21 | t.end(); 22 | }); 23 | 24 | test('typeOf :: * -> String', (t) => { 25 | t.equal(typeOf(false), 'boolean'); 26 | t.end(); 27 | }); 28 | 29 | test('typeOf :: * -> String', (t) => { 30 | t.equal(typeOf([]), 'array'); 31 | t.end(); 32 | }); 33 | 34 | test('typeOf :: * -> String', (t) => { 35 | t.equal(typeOf(new Array()), 'array'); // eslint-disable-line no-array-constructor 36 | t.end(); 37 | }); 38 | 39 | test('typeOf :: * -> String', (t) => { 40 | t.equal(typeOf({}), 'object'); 41 | t.end(); 42 | }); 43 | 44 | test('typeOf :: * -> String', (t) => { 45 | t.equal(typeOf(new Object()), 'object'); // eslint-disable-line no-new-object 46 | t.end(); 47 | }); 48 | 49 | test('typeOf :: * -> String', (t) => { 50 | t.equal(typeOf(null), 'null'); 51 | t.end(); 52 | }); 53 | 54 | test('typeOf :: * -> String', (t) => { 55 | t.equal(typeOf(undefined), 'undefined'); 56 | t.equal(typeOf(), 'undefined'); 57 | t.end(); 58 | }); 59 | -------------------------------------------------------------------------------- /test/functions/values.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { values } = require('../../src/functions'); 3 | 4 | const o = { 5 | a: 1, 6 | b: '2', 7 | c: [1, 2, 3], 8 | d: { a: 1, b: 2, c: 3 }, 9 | e: false, 10 | f: null, 11 | }; 12 | 13 | 14 | test('values :: {String: *} -> [*]', (t) => { 15 | t.deepEqual( 16 | values(o), 17 | [ 18 | 1, 19 | '2', 20 | [1, 2, 3], 21 | { a: 1, b: 2, c: 3 }, 22 | false, 23 | null, 24 | ]); 25 | t.end(); 26 | }); 27 | -------------------------------------------------------------------------------- /test/functions/zip.test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const { zip } = require('../../src/functions'); 3 | 4 | 5 | test('zip :: {String: *} -> [[String, *]]', (t) => { 6 | t.deepEqual( 7 | zip([1, 3], [2, 4]), 8 | [[1, 2], [3, 4]]); 9 | t.end(); 10 | }); 11 | --------------------------------------------------------------------------------