├── .editorconfig ├── .gitignore ├── LICENSE.md ├── README.md ├── lib └── ƒ.js ├── package.json ├── test └── test.js └── ƒ.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | #The MIT License (MIT) 2 | 3 | *Copyright (c) 2015 Mateo Gianolio* 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ƒ 2 | 3 | Implementing native versions of [Haskell](https://haskell.org) functions according to JavaScript ES6 standards. 4 | 5 | 1. [Haskell in ES6: Part 1](http://casualjavascript.com/?1) 6 | 2. [Haskell in ES6: Part 2](http://casualjavascript.com/?2) 7 | 8 | ### Install & usage 9 | 10 | ```bash 11 | $ git clone https://github.com/casualjs/f 12 | $ npm install 13 | ``` 14 | 15 | ```javascript 16 | var ƒ = require('./lib/ƒ'); 17 | ``` 18 | 19 | Test with: 20 | 21 | ```bash 22 | $ npm test 23 | ``` 24 | 25 | ### Functions 26 | 27 | **Miscellaneous functions:** 28 | 29 | * [x] [`(.)`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:.) as `ƒ.comp` 30 | * [x] [`flip`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:flip) as `ƒ.flip` 31 | * [x] [`until`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:until) as `ƒ.until` 32 | 33 | **List operations:** 34 | * [x] [`head`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:head) as `ƒ.head` 35 | * [x] [`last`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:last) as `ƒ.last` 36 | * [x] [`tail`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:tail) as `ƒ.tail` 37 | * [x] [`init`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:head) as `ƒ.init` 38 | 39 | **Special folds:** 40 | 41 | * [x] [`concat`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:concat) as `ƒ.concat` 42 | * [x] [`concatMap`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:concatMap) as `ƒ.concatMap` 43 | 44 | **Infinite lists:** 45 | 46 | * [x] [`iterate`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:iterate) as `ƒ.iterate` 47 | * [x] [`repeat`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:repeat) as `ƒ.repeat` 48 | * [x] [`cycle`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:cycle) as `ƒ.cycle` 49 | 50 | **Sublists:** 51 | 52 | * [x] [`take`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:take) as `ƒ.take` 53 | * [x] [`drop`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:drop) as `ƒ.drop` 54 | * [x] [`splitAt`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:splitAt) as `ƒ.splitAt` 55 | * [x] [`takeWhile`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:takeWhile) as `ƒ.takeWhile` 56 | * [x] [`dropWhile`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:dropWhile) as `ƒ.dropWhile` 57 | 58 | **Zipping and unzipping lists:** 59 | * [x] [`zip`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:zip) as `ƒ.zip` 60 | * [x] [`zipWith`](http://hackage.haskell.org/package/base-4.8.1.0/docs/Prelude.html#v:zipWith) as `ƒ.zipWith` 61 | -------------------------------------------------------------------------------- /lib/ƒ.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Function composition 5 | * @param ...fs functions to compose 6 | * @return composed function 7 | **/ 8 | ; 9 | 10 | (function (global, factory) { 11 | if (typeof define === "function" && define.amd) { 12 | define(['exports'], factory); 13 | } else if (typeof exports !== "undefined") { 14 | factory(exports); 15 | } else { 16 | var mod = { 17 | exports: {} 18 | }; 19 | factory(mod.exports); 20 | global._ = mod.exports; 21 | } 22 | })(this, function (exports) { 23 | Object.defineProperty(exports, "__esModule", { 24 | value: true 25 | }); 26 | exports.comp = comp; 27 | exports.flip = flip; 28 | exports.until = until; 29 | exports.head = head; 30 | exports.last = last; 31 | exports.tail = tail; 32 | exports.init = init; 33 | exports.concat = concat; 34 | exports.concatMap = concatMap; 35 | exports.iterate = iterate; 36 | exports.repeat = repeat; 37 | exports.cycle = cycle; 38 | exports.take = take; 39 | exports.drop = drop; 40 | exports.splitAt = splitAt; 41 | exports.takeWhile = takeWhile; 42 | exports.dropWhile = dropWhile; 43 | exports.zip = zip; 44 | exports.zipWith = zipWith; 45 | 46 | function _toConsumableArray(arr) { 47 | if (Array.isArray(arr)) { 48 | for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { 49 | arr2[i] = arr[i]; 50 | } 51 | 52 | return arr2; 53 | } else { 54 | return Array.from(arr); 55 | } 56 | } 57 | 58 | function comp() { 59 | for (var _len = arguments.length, fs = Array(_len), _key = 0; _key < _len; _key++) { 60 | fs[_key] = arguments[_key]; 61 | } 62 | 63 | return function (v) { 64 | for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) { 65 | args[_key2 - 1] = arguments[_key2]; 66 | } 67 | 68 | return fs.reduceRight(function (g, f) { 69 | return f.apply(undefined, [g].concat(args)); 70 | }, v); 71 | }; 72 | } 73 | 74 | function flip(f) { 75 | return function (a, b) { 76 | for (var _len3 = arguments.length, args = Array(_len3 > 2 ? _len3 - 2 : 0), _key3 = 2; _key3 < _len3; _key3++) { 77 | args[_key3 - 2] = arguments[_key3]; 78 | } 79 | 80 | return f.apply(undefined, [b, a].concat(args)); 81 | }; 82 | } 83 | 84 | function until(condition, f) { 85 | return function () { 86 | var r = f.apply(undefined, arguments); 87 | return condition(r) ? r : until(condition, f)(r); 88 | }; 89 | } 90 | 91 | function head(xs) { 92 | return xs[0]; 93 | } 94 | 95 | function last(xs) { 96 | return xs[xs.length - 1]; 97 | } 98 | 99 | function tail(xs) { 100 | return xs.slice(1); 101 | } 102 | 103 | function init(xs) { 104 | return xs.slice(0, -1); 105 | } 106 | 107 | function concat() { 108 | for (var _len4 = arguments.length, xs = Array(_len4), _key4 = 0; _key4 < _len4; _key4++) { 109 | xs[_key4] = arguments[_key4]; 110 | } 111 | 112 | return xs.reduce(function (a, b) { 113 | return a.concat(b); 114 | }); 115 | } 116 | 117 | function concatMap(f) { 118 | for (var _len5 = arguments.length, xs = Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) { 119 | xs[_key5 - 1] = arguments[_key5]; 120 | } 121 | 122 | return concat(xs.map(f)); 123 | } 124 | 125 | function iterate(f, x) { 126 | return Proxy.create({ 127 | get: function get(_, property) { 128 | if (isNaN(property)) return Infinity; 129 | var r = x; 130 | 131 | for (var i = 0; i < property; i++) { 132 | r = f(r); 133 | } 134 | 135 | return r; 136 | } 137 | }); 138 | } 139 | 140 | function repeat(x) { 141 | return Proxy.create({ 142 | get: function get(_, property) { 143 | return isNaN(property) ? Infinity : x; 144 | } 145 | }); 146 | } 147 | 148 | function cycle(xs) { 149 | return Proxy.create({ 150 | get: function get(_, property) { 151 | return isNaN(property) ? Infinity : xs[property % xs.length]; 152 | } 153 | }); 154 | } 155 | 156 | function take(n, xs) { 157 | if (n <= 0) return []; 158 | if (n >= xs.length) return xs; 159 | var r = []; 160 | 161 | for (var i = 0; i < n; i++) { 162 | r.push(xs[i]); 163 | } 164 | 165 | return r; 166 | } 167 | 168 | function drop(n, xs) { 169 | if (n <= 0) return xs; 170 | if (n >= xs.length) return []; 171 | 172 | if (xs.length === Infinity) { 173 | return Proxy.create({ 174 | get: function get(_, property) { 175 | return isNaN(property) ? Infinity : xs[property + n]; 176 | } 177 | }); 178 | } 179 | 180 | var r = []; 181 | 182 | for (var i = n; i < xs.length; i++) { 183 | r.push(xs[i]); 184 | } 185 | 186 | return r; 187 | } 188 | 189 | function splitAt(n, xs) { 190 | return [take(n, xs), drop(n, xs)]; 191 | } 192 | 193 | function takeWhile(f, xs) { 194 | var r = []; 195 | 196 | for (var i = 0; i < xs.length; i++) { 197 | if (!f(xs[i])) return r; 198 | r.push(xs[i]); 199 | } 200 | 201 | return r; 202 | } 203 | 204 | function dropWhile(f, xs) { 205 | return drop(takeWhile(f, xs).length, xs); 206 | } 207 | 208 | function zip() { 209 | var _Math; 210 | 211 | for (var _len6 = arguments.length, xs = Array(_len6), _key6 = 0; _key6 < _len6; _key6++) { 212 | xs[_key6] = arguments[_key6]; 213 | } 214 | 215 | var r = [], 216 | nple = [], 217 | length = (_Math = Math).min.apply(_Math, _toConsumableArray(xs.map(function (x) { 218 | return x.length; 219 | }))); 220 | 221 | for (var i = 0; i < length; i++) { 222 | xs.forEach(function (x) { 223 | return nple.push(x[i]); 224 | }); 225 | r.push(nple); 226 | nple = []; 227 | } 228 | 229 | return r; 230 | } 231 | 232 | function zipWith(op) { 233 | for (var _len7 = arguments.length, xs = Array(_len7 > 1 ? _len7 - 1 : 0), _key7 = 1; _key7 < _len7; _key7++) { 234 | xs[_key7 - 1] = arguments[_key7]; 235 | } 236 | 237 | return zip.apply(undefined, xs).map(function (x) { 238 | return x.reduce(op); 239 | }); 240 | } 241 | 242 | exports.default = { 243 | comp: comp, 244 | flip: flip, 245 | until: until, 246 | head: head, 247 | last: last, 248 | tail: tail, 249 | init: init, 250 | concat: concat, 251 | concatMap: concatMap, 252 | iterate: iterate, 253 | repeat: repeat, 254 | cycle: cycle, 255 | take: take, 256 | drop: drop, 257 | splitAt: splitAt, 258 | takeWhile: takeWhile, 259 | dropWhile: dropWhile, 260 | zip: zip, 261 | zipWith: zipWith 262 | }; 263 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "f", 3 | "version": "0.1.0", 4 | "description": "Native ES6 implementations of Haskell functions", 5 | "main": "./lib/ƒ.js", 6 | "directories": { 7 | "test": "test", 8 | "lib": "lib" 9 | }, 10 | "babel": { 11 | "env": { 12 | "production": { 13 | "plugins": [ 14 | "transform-es2015-modules-umd" 15 | ] 16 | } 17 | }, 18 | "presets": [ 19 | "es2015" 20 | ] 21 | }, 22 | "scripts": { 23 | "build:lib": "BABEL_ENV=production babel ./ƒ.js --out-dir lib", 24 | "build": "npm run build:lib", 25 | "prepublish": "npm test && npm run build", 26 | "test": "mocha --harmony-proxies --compilers js:babel-core/register" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/casualjs/f.git" 31 | }, 32 | "keywords": [ 33 | "ƒ", 34 | "haskell", 35 | "es6", 36 | "native" 37 | ], 38 | "author": "Mateo Gianolio", 39 | "license": "MIT", 40 | "bugs": { 41 | "url": "https://github.com/casualjs/f/issues" 42 | }, 43 | "homepage": "https://github.com/casualjs/f#readme", 44 | "devDependencies": { 45 | "babel": "^6.1.18", 46 | "babel-cli": "^6.1.18", 47 | "babel-core": "^6.1.21", 48 | "babel-plugin-transform-es2015-modules-umd": "^6.1.18", 49 | "babel-preset-es2015": "^6.1.18", 50 | "mocha": "^2.3.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import assert from 'assert'; 4 | import ƒ from './../ƒ'; 5 | 6 | describe('ƒ.comp()', 7 | () => it('should be able to compose three functions correctly', 8 | () => { 9 | var add = x => x + x, 10 | pow = x => x * x, 11 | inv = x => 1 / x, 12 | comp = ƒ.comp(add, pow, inv); 13 | 14 | assert.equal(2, comp(1)); 15 | assert.equal(1/8, comp(4)); 16 | } 17 | ) 18 | ); 19 | 20 | describe('ƒ.flip()', 21 | () => it('should apply function with flipped arguments', 22 | () => { 23 | var add = (a, b) => a / b, 24 | three = (a, b, c) => [a, b, c], 25 | flip = ƒ.flip(add); 26 | 27 | assert.equal(1/2, flip(10, 5)); 28 | assert.equal(10, flip(1, 10)); 29 | 30 | flip = ƒ.flip(three); 31 | assert.deepEqual([2, 1, 3], flip(1, 2, 3)); 32 | } 33 | ) 34 | ); 35 | 36 | describe('ƒ.until()', 37 | () => it('should run until condition is satisfied', 38 | () => { 39 | var condition = x => x > 100, 40 | inc = x => x + 1, 41 | until = ƒ.until(condition, inc); 42 | assert.equal(101, until(0)); 43 | 44 | condition = x => x === 5; 45 | until = ƒ.until(condition, inc); 46 | assert.equal(5, until(3)); 47 | } 48 | ) 49 | ); 50 | 51 | describe('ƒ.head()', 52 | () => it('should return first argument', 53 | () => assert.equal(5, ƒ.head([5, 27, 3, 1])) 54 | ) 55 | ); 56 | 57 | describe('ƒ.last()', 58 | () => it('should return last argument', 59 | () => assert.equal(1, ƒ.last([5, 27, 3, 1])) 60 | ) 61 | ); 62 | 63 | describe('ƒ.tail()', 64 | () => it('should return all but first argument', 65 | () => assert.deepEqual([27, 3, 1], ƒ.tail([5, 27, 3, 1])) 66 | ) 67 | ); 68 | 69 | describe('ƒ.init()', 70 | () => it('should return all but last argument', 71 | () => assert.deepEqual([5, 27, 3], ƒ.init([5, 27, 3, 1])) 72 | ) 73 | ); 74 | 75 | describe('ƒ.concat()', 76 | () => { 77 | it('should concatenate args', 78 | () => assert.deepEqual([5, 27, 3], ƒ.concat([5], [27], [3])) 79 | ); 80 | it('should work on arrays', 81 | () => assert.deepEqual([5, 27, 3], ƒ.concat(...[[5], [27], [3]])) 82 | ); 83 | } 84 | ); 85 | 86 | describe('ƒ.concatMap()', 87 | () => { 88 | var map = x => 'hi ' + x; 89 | it('should map and concatenate results', 90 | () => assert.deepEqual(['hi 1', 'hi 2', 'hi 3'], ƒ.concatMap(map, 1, [[2]], 3)) 91 | ); 92 | it('should work on arrays', 93 | () => assert.deepEqual(['hi 1', 'hi 2', 'hi 3'], ƒ.concatMap.bind(null, map)(...[1, [2], [[3]]])) 94 | ); 95 | } 96 | ); 97 | 98 | describe('ƒ.iterate()', 99 | () => it('should return a proxy simulating an infinite list of repeated applications of f to x', 100 | () => { 101 | var iterate = ƒ.iterate(x => x * 2, 1); 102 | assert.equal(2, iterate[1]); 103 | assert.equal(16, iterate[4]); 104 | assert.equal(128, iterate[7]); 105 | } 106 | ) 107 | ); 108 | 109 | describe('ƒ.repeat()', 110 | () => it('should return a proxy simulating an infinite list of xs', 111 | () => { 112 | var repeat = ƒ.repeat(10); 113 | assert.equal(10, repeat[0]); 114 | assert.equal(10, repeat[10]); 115 | assert.equal(10, repeat[9999]); 116 | } 117 | ) 118 | ); 119 | 120 | describe('ƒ.cycle()', 121 | () => it('should return a proxy simulating an infinite list of repeated cycles of xs', 122 | () => { 123 | var list = [0, 1, 2], 124 | cycle = ƒ.cycle(list); 125 | for (var i = 0; i < 10; i++) 126 | assert.equal(i % list.length, cycle[i]); 127 | } 128 | ) 129 | ); 130 | 131 | describe('ƒ.take()', 132 | () => { 133 | it('should take n elements from xs or xs if n >= xs.length', 134 | () => { 135 | var list = [0, 1, 2, 3, 4, 5]; 136 | 137 | assert.deepEqual([0, 1, 2], ƒ.take(3, list)); 138 | assert.deepEqual([0, 1, 2, 3, 4, 5], ƒ.take(10, list)); 139 | } 140 | ); 141 | 142 | it('should work with infinite lists', 143 | () => assert.deepEqual([0, 1, 2, 0, 1, 2], ƒ.take(6, ƒ.cycle([0, 1, 2]))) 144 | ); 145 | } 146 | ); 147 | 148 | describe('ƒ.drop()', 149 | () => { 150 | it('should drop n elements from xs or [] if n >= xs.length', 151 | () => { 152 | var list = [0, 1, 2, 3, 4, 5]; 153 | 154 | assert.deepEqual([3, 4, 5], ƒ.drop(3, list)); 155 | assert.deepEqual([], ƒ.drop(10, list)); 156 | } 157 | ); 158 | 159 | it('should work with infinite lists', 160 | () => assert.deepEqual([2, 0, 1, 2], ƒ.take(4, ƒ.drop(2, ƒ.cycle([0, 1, 2])))) 161 | ); 162 | } 163 | ); 164 | 165 | describe('ƒ.splitAt()', 166 | () => { 167 | it('should return a tuple with [take(n, xs), drop(n, xs)]', 168 | () => { 169 | assert.deepEqual([[0, 1, 2], [3, 4, 5]], ƒ.splitAt(3, [0, 1, 2, 3, 4, 5])); 170 | assert.deepEqual([[], [0, 1, 2]], ƒ.splitAt(-1, [0, 1, 2])); 171 | assert.deepEqual([[0, 1, 2], []], ƒ.splitAt(3, [0, 1, 2])); 172 | } 173 | ); 174 | 175 | it('should work with infinite lists', 176 | () => assert.deepEqual([2, 0, 1, 2], ƒ.take(4, ƒ.drop(2, ƒ.cycle([0, 1, 2])))) 177 | ); 178 | } 179 | ); 180 | 181 | describe('ƒ.takeWhile()', 182 | () => { 183 | it('should return the longest prefix of xs of elements that satisfy f', 184 | () => assert.deepEqual([0, 1, 2], ƒ.takeWhile(x => x < 3, [0, 1, 2, 3, 4, 5])) 185 | ); 186 | 187 | it('should work with infinite lists', 188 | () => assert.deepEqual([0, 1, 2], ƒ.takeWhile(x => x < 3, ƒ.cycle([0, 1, 2, 3, 4, 5]))) 189 | ); 190 | } 191 | ); 192 | 193 | describe('ƒ.dropWhile()', 194 | () => { 195 | it('should return the suffix remaining after takeWhile(f, xs)', 196 | () => assert.deepEqual([3, 4, 5], ƒ.dropWhile(x => x < 3, [0, 1, 2, 3, 4, 5])) 197 | ); 198 | 199 | it('should work with infinite lists', 200 | () => assert.deepEqual(3, ƒ.dropWhile(x => x < 3, ƒ.cycle([0, 1, 2, 3, 4, 5]))[0]) 201 | ); 202 | } 203 | ); 204 | 205 | describe('ƒ.zip()', 206 | () => it('should form a list of n-ples from n arrays', 207 | () => { 208 | var a = [0, 1, 2], 209 | b = [3, 4, 5], 210 | c = [6, 7, 8]; 211 | 212 | assert.deepEqual([[0, 3], [1, 4], [2, 5]], ƒ.zip(a, b)); 213 | assert.deepEqual([[0, 3, 6], [1, 4, 7], [2, 5, 8]], ƒ.zip(a, b, c)); 214 | } 215 | ) 216 | ); 217 | 218 | describe('ƒ.zipWith()', 219 | () => it('should zip n arrays and reduce the resulting n-ples with specified operator', 220 | () => { 221 | var a = [0, 1, 2], 222 | b = [3, 4, 5], 223 | c = [6, 7, 8], 224 | add = (a, b) => a + b, 225 | mul = (a, b) => a * b; 226 | 227 | assert.deepEqual([9, 12, 15], ƒ.zipWith(add, a, b, c)); 228 | assert.deepEqual([18, 28, 40], ƒ.zipWith(mul, b, c)); 229 | } 230 | ) 231 | ); 232 | -------------------------------------------------------------------------------- /ƒ.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /** 4 | * Function composition 5 | * @param ...fs functions to compose 6 | * @return composed function 7 | **/ 8 | export function comp (...fs) { 9 | return (v, ...args) => 10 | fs.reduceRight( 11 | (g, f) => f(g, ...args), v 12 | ); 13 | } 14 | 15 | /** 16 | * Flip function arguments 17 | * @param f function to flip 18 | * @return f applied with args in reverse order 19 | **/ 20 | export function flip (f) { 21 | return (a, b, ...args) => 22 | f(b, a, ...args); 23 | } 24 | 25 | /** 26 | * Applies a function which is passed as the second argument to 27 | * the third argument and it comapares the result with the condition, 28 | * if the condition evaluates to true, it prints the result, if not, 29 | * it passes the result to the function and repeats the cycle as long 30 | * as the condition is matched 31 | * @param condition condition to be applied to f 32 | * @param f function to match against 33 | * @return result if condition is true else repeat cycle 34 | **/ 35 | export function until (condition, f){ 36 | return (...args) => { 37 | var r = f(...args); 38 | return condition(r) ? r : until(condition, f)(r); 39 | }; 40 | } 41 | 42 | /** 43 | * List operations 44 | **/ 45 | export function head (xs) { return xs[0]; } 46 | export function last (xs) { return xs[xs.length - 1]; } 47 | export function tail (xs) { return xs.slice(1); } 48 | export function init (xs) { return xs.slice(0, -1); } 49 | 50 | /** 51 | * Special folds 52 | **/ 53 | export function concat (...xs) { 54 | return xs.reduce( 55 | (a, b) => a.concat(b) 56 | ); 57 | } 58 | 59 | export function concatMap (f, ...xs) { 60 | return concat(xs.map(f)); 61 | } 62 | 63 | /** 64 | * Returns an infinite list of repeated applications of f to x 65 | * @param f function to iterate with 66 | * @param x initial value 67 | **/ 68 | export function iterate (f, x) { 69 | return Proxy.create({ 70 | get: (_, property) => { 71 | if (isNaN(property)) 72 | return Infinity; 73 | 74 | var r = x; 75 | for (var i = 0; i < property; i++) 76 | r = f(r); 77 | return r; 78 | } 79 | }); 80 | } 81 | 82 | /** 83 | * Returns an infinite list, with x the value of every element 84 | * @param x value 85 | **/ 86 | export function repeat (x) { 87 | return Proxy.create({ 88 | get: (_, property) => 89 | isNaN(property) ? 90 | Infinity : x 91 | }); 92 | } 93 | 94 | /** 95 | * Ties a finite list into a circular one, or equivalently, 96 | * the infinite repetition of the original list 97 | * @param xs list to be cycled 98 | **/ 99 | export function cycle (xs) { 100 | return Proxy.create({ 101 | get: (_, property) => 102 | isNaN(property) ? 103 | Infinity : xs[property % xs.length] 104 | }); 105 | } 106 | 107 | /** 108 | * Applied to a list xs, returns the prefix of xs of length n 109 | * @param n number of elements to take 110 | * @param xs list to take from 111 | **/ 112 | export function take (n, xs) { 113 | if (n <= 0) return []; 114 | if (n >= xs.length) return xs; 115 | 116 | var r = []; 117 | for (var i = 0; i < n; i++) 118 | r.push(xs[i]); 119 | 120 | return r; 121 | } 122 | 123 | /** 124 | * Returns the suffix of xs after the first n elements 125 | * @param n number of elements to drop 126 | * @param xs list to drop from 127 | **/ 128 | export function drop (n, xs) { 129 | if (n <= 0) return xs; 130 | if (n >= xs.length) return []; 131 | 132 | if (xs.length === Infinity) { 133 | return Proxy.create({ 134 | get: (_, property) => 135 | isNaN(property) ? 136 | Infinity : xs[property + n] 137 | }); 138 | } 139 | 140 | var r = []; 141 | for (var i = n; i < xs.length; i++) 142 | r.push(xs[i]); 143 | 144 | return r; 145 | } 146 | 147 | /** 148 | * Returns a tuple where first element is xs prefix of length n and 149 | * second element is the remainder of the list 150 | * @param n index to split at 151 | * @param xs list to split 152 | **/ 153 | export function splitAt (n, xs) { 154 | return [take(n, xs), drop(n, xs)]; 155 | } 156 | 157 | /** 158 | * Applied to a predicate f and a list xs, returns the longest 159 | * prefix (possibly empty) of xs of elements that satisfy f 160 | * @param f predicate to satisfy 161 | * @param xs list to take from 162 | **/ 163 | export function takeWhile (f, xs) { 164 | var r = []; 165 | for (var i = 0; i < xs.length; i++) { 166 | if (!f(xs[i])) 167 | return r; 168 | r.push(xs[i]); 169 | } 170 | 171 | return r; 172 | } 173 | 174 | /** 175 | * Returns the suffix remaining after takeWhile 176 | * @param f predicate to satisfy 177 | * @param xs list to drop from 178 | **/ 179 | export function dropWhile (f, xs) { 180 | return drop(takeWhile(f, xs).length, xs); 181 | } 182 | 183 | /** 184 | * Zip two arrays into a list of n-ples 185 | * @param ...xs arrays to zip 186 | * @return a list of of n-ples 187 | **/ 188 | export function zip (...xs) { 189 | var r = [], 190 | nple = [], 191 | length = Math.min(...xs.map(x => x.length)); 192 | 193 | for (var i = 0; i < length; i++) { 194 | xs.forEach( 195 | x => nple.push(x[i]) 196 | ); 197 | 198 | r.push(nple); 199 | nple = []; 200 | } 201 | 202 | return r; 203 | } 204 | 205 | /** 206 | * Generalises zip by zipping with the function given 207 | * as the first argument, instead of a tupling function. 208 | * @param op function to zip with 209 | * @param ...xs arrays to zip 210 | * @return array zipped with the op function 211 | **/ 212 | export function zipWith (op, ...xs) { 213 | return zip(...xs).map( 214 | x => x.reduce(op) 215 | ); 216 | } 217 | 218 | export default { 219 | comp, 220 | flip, 221 | until, 222 | head, 223 | last, 224 | tail, 225 | init, 226 | concat, 227 | concatMap, 228 | iterate, 229 | repeat, 230 | cycle, 231 | take, 232 | drop, 233 | splitAt, 234 | takeWhile, 235 | dropWhile, 236 | zip, 237 | zipWith 238 | }; 239 | --------------------------------------------------------------------------------