├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── README.md ├── example └── sqrt.js ├── package.json ├── source ├── error.js ├── index.js ├── lib.js ├── list.js └── ord.js └── test ├── concat-test.js ├── cons-test.js ├── cycle-test.js ├── drop-test.js ├── dropWhile-test.js ├── emptyList-test.js ├── filter-test.js ├── fromArrayToList-test.js ├── fromListToArray-test.js ├── fromListToString-test.js ├── fromStringToList-test.js ├── global.js ├── head-test.js ├── index-test.js ├── init-test.js ├── isEmpty-test.js ├── isList-test.js ├── iterate-test.js ├── last-test.js ├── length-test.js ├── list-class-test.js ├── list-test.js ├── listAppend-test.js ├── listInf-test.js ├── listInfBy-test.js ├── listRange-test.js ├── listRangeBy-test.js ├── map-test.js ├── repeat-test.js ├── replicate-test.js ├── reverse-test.js ├── sort-test.js ├── sortBy-test.js ├── tail-test.js ├── take-test.js └── takeWhile-test.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "test": { 4 | "plugins": ["istanbul"] 5 | } 6 | }, 7 | "presets": ["es2015"], 8 | "plugins": [ 9 | ["transform-runtime", { 10 | "polyfill": true, 11 | "regenerator": true 12 | }] 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "es6": true 5 | }, 6 | "parserOptions": { 7 | "ecmaVersion": 6, 8 | "sourceType": "module" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | node_modules 3 | distribution 4 | coverage 5 | .nyc_output 6 | *.lcov 7 | *.log 8 | *~ 9 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": [ "describe, it" ] 3 | } 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | source 3 | test 4 | example 5 | coverage 6 | .nyc_output 7 | *.lcov 8 | *.log 9 | *~ 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - '6' 10 | before_install: 11 | - npm i -g npm@^2.0.0 12 | before_script: 13 | - npm prune 14 | - npm install -g codeclimate-test-reporter 15 | script: npm run cover:report 16 | after_script: 17 | - codeclimate-test-reporter < coverage.lcov 18 | after_success: 19 | - npm run semantic-release 20 | branches: 21 | except: 22 | - /^v\d+\.\d+\.\d+$/ 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | 6 | ### 0.0.1 (2016-08-20) 7 | 8 | #### Features 9 | 10 | * create lazy-linked-lists package ([fde2764d10d30ca299aafd956f02a2d29ba8ba74](https://github.com/sjsyrek/lazy-linked-lists/commit/fde2764d10d30ca299aafd956f02a2d29ba8ba74)) 11 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Pull requests are welcome. 4 | 5 | All contributed code should conform to the following requirements: 6 | - functional in style, preferably following the example of haskell 7 | - written in a style similar to that of the rest of the library 8 | - presented in accordance with the standard style 9 | - checked with eslint 10 | - fully tested using mocha and should 11 | - fully covered, as shown in nyc/istanbul reports 12 | - fully documented using jsdoc style, with all additions, deletions, and changes reported in CHANGELOG.md 13 | - open source (including dependencies) on the same terms as the rest of this library 14 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | lazy-linked-lists 2 | https://github.com/sjsyrek/lazy-linked-lists 3 | 4 | Copyright (c) 2016, Steven J. Syrek 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any purpose 7 | with or without fee is hereby granted, provided that the above copyright notice 8 | and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 11 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 12 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 13 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 14 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 15 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 16 | THIS SOFTWARE. 17 | 18 | https://www.isc.org/downloads/software-support-policy/isc-license/ 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Lazy and infinite linked lists for JavaScript 2 | 3 | [![license](https://img.shields.io/badge/license-ISC-blue.svg)](https://github.com/sjsyrek/lazy-linked-lists/blob/master/LICENSE.txt) 4 | [![build status](https://travis-ci.org/sjsyrek/lazy-linked-lists.svg?branch=master)](https://travis-ci.org/sjsyrek/lazy-linked-lists) 5 | [![test coverage](https://codeclimate.com/github/sjsyrek/lazy-linked-lists/badges/coverage.svg)](https://codeclimate.com/github/sjsyrek/lazy-linked-lists/coverage) 6 | [![bitHound score](https://www.bithound.io/github/sjsyrek/lazy-linked-lists/badges/score.svg)](https://www.bithound.io/github/sjsyrek/lazy-linked-lists) 7 | [![dependencies status](https://david-dm.org/sjsyrek/lazy-linked-lists.svg)](https://david-dm.org/sjsyrek/lazy-linked-lists) 8 | [![devDependencies status](https://david-dm.org/sjsyrek/lazy-linked-lists/dev-status.svg)](https://david-dm.org/sjsyrek/lazy-linked-lists?type=dev) 9 | [![dependency status](https://dependencyci.com/github/sjsyrek/lazy-linked-lists/badge)](https://dependencyci.com/github/sjsyrek/lazy-linked-lists) 10 | [![style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/) 11 | [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) 12 | [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/334/badge)](https://bestpractices.coreinfrastructure.org/projects/334) 13 | 14 | [![NPM](https://nodei.co/npm/lazy-linked-lists.png?downloads=true)](https://nodei.co/npm/lazy-linked-lists/) 15 | 16 | > A spectre is haunting ECMAScript—the spectre of tail call optimization. 17 | > — Karl Marxdown 18 | 19 | ## About 20 | 21 | This library is adapted from [maryamyriameliamurphies.js](https://github.com/sjsyrek/maryamyriameliamurphies.js) with several modifications. It includes functions for creating both eagerly and lazily-evaluated linked list data structures, including infinite lists, and a core subset of functions for working with them. Unlike **maryamyriameliamurphies.js**, however, **lazy-linked-lists** does not implement function currying, partial application, or type checking. It does, however, implement most of the standard [Haskell](https://www.haskell.org) type classes as instance methods, and it also implements the [ES2015 iteration protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols), so you can use them in `for...of` loops or otherwise as regular iterators. 22 | 23 | The lazy lists provided by this library are implemented using the new `Proxy` API in ES2015. Briefly, the lists returned from most of the list constructors are actually hidden behind proxy objects that trap references to their "tail" property, so that list elements, produced by an ES2015 generator function, are only evaluated on demand. Obviously, if you request the entire tail (by, for example, calling the `length()` function on a list), then the entire tail will be evaluated. You will want to avoid doing that with infinite lists. 24 | 25 | Note that as of this writing, most implementations of the ES2015 standard do not yet support [proper tail calls](http://www.2ality.com/2015/06/tail-call-optimization.html). But support is on its way! The newest versions of node and Safari have already rolled it out, and other vendors are surely not far behind. See the top line of this [compatibility chart](https://kangax.github.io/compat-table/es6/) to track the progress of the feature that will make recursively defined, fully-functional, high octane linked lists in JavaScript a reality. Until that fateful day, however, you may be limited to lists of only 10,000 elements or so. 26 | 27 | For further details, see the [documentation](http://sjsyrek.github.io/maryamyriameliamurphies.js/) for **maryamyriameliamurphies.js**. 28 | 29 | ##### [Try it now with Tonic](https://tonicdev.com/npm/lazy-linked-lists) 30 | 31 | ## Examples 32 | 33 | Linked list, eagerly evaluated: 34 | ```js 35 | const lst = list(1,2,3,4,5,6,7,8,9,10); 36 | lst.valueOf(); // => '[1:2:3:4:5:6:7:8:9:10:[]]' 37 | ``` 38 | 39 | Linked list, lazily evaluated: 40 | ```js 41 | const lst = listRange(1, 10); 42 | lst.valueOf(); // => '[1:2:3:4:5:6:7:8:9:10:[]]' 43 | ``` 44 | 45 | Linked list, lazily evaluated with a user-defined step function: 46 | ```js 47 | const lst1 = listRangeBy(0, 100, x => x + 10); 48 | const lst2 = listRangeBy(10, 0, x => x - 1); 49 | lst1.valueOf(); // => '[0:10:20:30:40:50:60:70:80:90:100:[]]' 50 | lst2.valueOf(); // => '[10:9:8:7:6:5:4:3:2:1:[]]' 51 | ``` 52 | 53 | Iterating over a linked list: 54 | ```js 55 | const lst = listRange(1, 5); 56 | for (let value of lst) { console.log(value); } 57 | // 1 58 | // 2 59 | // 3 60 | // 4 61 | // 5 62 | ``` 63 | 64 | Infinite list: 65 | ```js 66 | const lst = listInf(1); 67 | take(10, lst).valueOf(); // => '[1:2:3:4:5:6:7:8:9:10:[]]' 68 | lst.valueOf(); // => RangeError: Maximum call stack size exceeded 69 | ``` 70 | 71 | Infinite list with a user-defined step function: 72 | ```js 73 | const lst = listInfBy(0, x => x + 10); 74 | take(11, lst).valueOf(); // => '[0:10:20:30:40:50:60:70:80:90:100:[]]' 75 | ``` 76 | 77 | Haskell-style type class action: 78 | ```js 79 | const lst1 = list(1,2,3); 80 | const lst2 = list(3,2,1); 81 | 82 | // Eq 83 | lst1.isEq(list(1,2,3)); // => true 84 | lst1.isEq(lst2); // => false 85 | 86 | // Ord 87 | lst1.compare(lst2); // => Symbol() 88 | lst1.compare(lst2) === LT; // => true 89 | lst1.isLessThan(lst2); // => true 90 | lst1.isGreaterThan(lst2); // => false 91 | 92 | // Monoid 93 | lst1.mappend(lst1.mempty()); // => '[1:2:3:[]]' 94 | lst1.mappend(lst2); // => '[1:2:3:4:5:6:[]]' 95 | 96 | // Foldable 97 | lst1.foldr((x,y) => x * y, 1); // => 6 98 | 99 | // Traversable 100 | lst1.traverse(x => list(x * 10)); // => '[[10:20:30:[]]:[]]' 101 | 102 | // Functor 103 | lst1.fmap(x => x * 10); // => '[10:20:30:[]]' 104 | 105 | // Applicative 106 | const f = x => x * 10; 107 | const fs1 = list(f); 108 | const fs2 = list(f,f,f); 109 | fs1.ap(lst1); // => '[10:20:30:[]]' 110 | fs2.ap(lst1); // => '[10:20:30:10:20:30:10:20:30:[]]' 111 | 112 | // Monad 113 | lst1.flatMap(x => list(x * 10)); // => '[10:20:30:[]]' 114 | lst1.then(lst2); // => '[4:5:6:4:5:6:4:5:6:[]]' 115 | const stringify = x => list(`${x}`); 116 | lst1.flatMap(x => list(x, x * 10, x * x)).flatMap(stringify); // => '[110122043309]' 117 | ``` 118 | 119 | Other fun stuff: 120 | ```js 121 | const lst1 = listInfBy(0, x => x + 2); 122 | const lst2 = take(10, lst1); 123 | const lst3 = reverse(lst2); 124 | const lst4 = sort(lst3); 125 | lst3.valueOf(); // => '[18:16:14:12:10:8:6:4:2:0:[]]' 126 | lst4.valueOf(); // => '[0:2:4:6:8:10:12:14:16:18:[]]' 127 | 128 | const lst5 = iterate(x => x * 2, 1); 129 | const lst6 = take(10, lst5); 130 | lst6.valueOf(); // => '[1:2:4:8:16:32:64:128:256:512:[]]' 131 | index(lst6, 10); // => Error: *** Exception: index: range error 132 | index(lst5, 10); // => 1024 133 | 134 | const lst7 = repeat(3); 135 | const lst8 = take(10, lst7); 136 | lst8.valueOf(); // => '[3:3:3:3:3:3:3:3:3:3:[]]' 137 | index(lst7, 100); // => 3 138 | 139 | const lst9 = replicate(10, 3); 140 | lst9.valueOf(); // => [3:3:3:3:3:3:3:3:3:3:[]] 141 | 142 | const lst10 = list(1,2,3); 143 | const lst11 = cycle(lst10); 144 | const lst12 = take(9, lst11); 145 | lst12.valueOf(); // => [1:2:3:1:2:3:1:2:3:[]] 146 | index(lst11, 99); // => 1 147 | index(lst11, 100); // => 2 148 | index(lst11, 101); // => 3 149 | ``` 150 | 151 | See also the files in the example directory. 152 | 153 | ### How to install and use 154 | 155 | - [Install with npm](https://www.npmjs.com/package/lazy-linked-lists) `npm install --save-dev lazy-linked-lists`. _Do not_ install this package globally. 156 | - If you're transpiling >=ES2015 code with [Babel](http://babeljs.io), put `import * as lazy from 'lazy-linked-lists';` at the top of your script files. 157 | - Or, to pollute your namespace, import functions individually: `import {listRange, listRangeBy} from 'lazy-linked-lists';`. 158 | - Or, if you aren't transpiling (or you're old school), use node's require syntax: `const lazy = require('lazy-linked-lists');`. 159 | 160 | ### How to develop 161 | 162 | - [Fork this repo](https://help.github.com/articles/fork-a-repo/) and [clone it locally](https://help.github.com/articles/cloning-a-repository/). 163 | - `npm install` to download the dependencies. 164 | - `npm run compile` to run Babel on ES2015 code in `./source` and output transpiled ES5 code to `./distribution`. 165 | - `npm run lint` to run ESlint to check the source code for errors. 166 | - `npm test` to run Mocha on the test code in `./test`. 167 | - `npm run cover` to run nyc on the source code and generate testing coverage reports. 168 | - `npm run clean` to delete all files in `./distribution`. 169 | 170 | #### See also 171 | 172 | * [lazy.js](https://github.com/dtao/lazy.js) 173 | * [node-lazy](https://github.com/pkrumins/node-lazy) 174 | * [lazy-list](https://github.com/luochen1990/lazy-list) 175 | * [js-list-lazy](https://github.com/dankogai/js-list-lazy) 176 | * [fantasy-land](https://github.com/fantasyland/fantasy-land) 177 | * [js-algebraic](https://github.com/tel/js-algebraic) 178 | -------------------------------------------------------------------------------- /example/sqrt.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lazy-linked-lists 3 | * Lazy and infinite linked lists for JavaScript. 4 | * 5 | * example/sqrt.js 6 | * 7 | * Examples of usage for lazy-linked-lists. 8 | * @license ISC 9 | */ 10 | 11 | import * as lazy from '../source' 12 | 13 | /** 14 | * Compute the square root of a number using infinite lists with the Newton-Raphson method. 15 | * Adapted from "Why Functional Programming Matters" by John Hughes. 16 | * @param {number} a0 - initial estimate 17 | * @param {number} eps - tolerance 18 | * @param {number} n - number 19 | * @returns {number} The square root of `n` 20 | * @kind function 21 | * @example 22 | * sqrt(1,0,2); // => 1.414213562373095 23 | * sqrt(1,0,144); // => 12 24 | * relativeSqrt(1,0,144); // optimized version for very small and very large numbers 25 | */ 26 | export const sqrt = (a0, eps, n) => within(eps, lazy.iterate(next.bind(null, n), a0)) 27 | 28 | export const relativeSqrt = (a0, eps, n) => relative(eps, lazy.iterate(next.bind(null, n), a0)) 29 | 30 | const next = (n, x) => (x + n / x) / 2 31 | 32 | const within = (eps, rest) => { 33 | let a = lazy.index(rest, 0) 34 | let b = lazy.index(rest, 1) 35 | return Math.abs(a - b) <= eps ? b : within(eps, lazy.drop(1, rest)) 36 | } 37 | 38 | const relative = (eps, rest) => { 39 | let a = lazy.index(rest, 0) 40 | let b = lazy.index(rest, 1) 41 | return Math.abs(a - b) <= eps * Math.abs(b) ? b : relative(eps, lazy.drop(1, rest)) 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lazy-linked-lists", 3 | "description": "Lazy and infinite linked lists for JavaScript.", 4 | "author": "Steven J. Syrek", 5 | "license": "ISC", 6 | "homepage": "https://github.com/sjsyrek/lazy-linked-lists", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/sjsyrek/lazy-linked-lists.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/sjsyrek/lazy-linked-lists/issues" 13 | }, 14 | "keywords": [ 15 | "linked", 16 | "lists", 17 | "lazy", 18 | "infinite", 19 | "data", 20 | "structures", 21 | "babel", 22 | "es6", 23 | "es2015", 24 | "proxy", 25 | "haskell", 26 | "functional", 27 | "fp" 28 | ], 29 | "main": "./distribution/index.js", 30 | "directories": { 31 | "lib": "distribution", 32 | "test": "test", 33 | "example": "example" 34 | }, 35 | "engines": { 36 | "node": ">=6.0.0" 37 | }, 38 | "nyc": { 39 | "sourceMap": false, 40 | "instrument": false 41 | }, 42 | "standard": { 43 | "ignore": [ 44 | "distribution" 45 | ], 46 | "env": [ 47 | "mocha" 48 | ] 49 | }, 50 | "devDependencies": { 51 | "babel-cli": "^6.11.4", 52 | "babel-core": "^6.13.2", 53 | "babel-plugin-istanbul": "^2.0.0", 54 | "babel-plugin-transform-runtime": "^6.12.0", 55 | "babel-preset-es2015": "^6.13.2", 56 | "babel-register": "^6.11.6", 57 | "bithound": "^1.7.0", 58 | "codeclimate-test-reporter": "^0.4.0", 59 | "cross-env": "^3.1.0", 60 | "eslint": "^3.6.0", 61 | "mocha": "^3.0.2", 62 | "nyc": "^8.1.0", 63 | "semantic-release": "^4.3.5", 64 | "should": "^11.1.0", 65 | "standard": "^8.4.0" 66 | }, 67 | "dependencies": { 68 | "babel-runtime": "^6.11.6" 69 | }, 70 | "scripts": { 71 | "clean": "rm -r distribution/*", 72 | "compile": "babel source --out-dir distribution", 73 | "lint": "eslint source", 74 | "test": "cross-env NODE_ENV=test mocha --compilers js:babel-register", 75 | "watch": "npm test -- --watch", 76 | "cover": "nyc --reporter html --reporter text -- npm -s test", 77 | "cover:report": "nyc npm test && nyc report --reporter=text-lcov > coverage.lcov", 78 | "prepublish": "npm run compile", 79 | "semantic-release": "semantic-release pre && npm publish && semantic-release post", 80 | "standard": "standard", 81 | "fix": "standard --fix" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /source/error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lazy-linked-lists 3 | * Lazy and infinite linked lists for JavaScript. 4 | * 5 | * source/error.js 6 | * 7 | * Errors. 8 | * @license ISC 9 | */ 10 | 11 | export class EmptyListError extends Error { 12 | constructor (message) { 13 | super(message) 14 | this.name = 'EmptyListError' 15 | } 16 | } 17 | export class OutOfRangeError extends Error { 18 | constructor (message) { 19 | super(message) 20 | this.name = 'OutOfRangeError' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /source/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lazy-linked-lists 3 | * Lazy and infinite linked lists for JavaScript. 4 | * 5 | * source/index.js 6 | * 7 | * Top level index. 8 | */ 9 | 10 | export { 11 | LT, 12 | GT, 13 | EQ 14 | } from './ord' 15 | 16 | export { 17 | emptyList, 18 | list, 19 | listRange, 20 | listRangeBy, 21 | listAppend, 22 | cons, 23 | head, 24 | last, 25 | tail, 26 | init, 27 | length, 28 | isList, 29 | isEmpty, 30 | fromArrayToList, 31 | fromListToArray, 32 | fromListToString, 33 | fromStringToList, 34 | concat, 35 | index, 36 | filter, 37 | map, 38 | reverse, 39 | sort, 40 | sortBy, 41 | take, 42 | drop, 43 | takeWhile, 44 | dropWhile, 45 | listInf, 46 | listInfBy, 47 | iterate, 48 | repeat, 49 | replicate, 50 | cycle 51 | } from './lib' 52 | 53 | export { 54 | EmptyListError, 55 | OutOfRangeError 56 | } from './error' 57 | -------------------------------------------------------------------------------- /source/lib.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lazy-linked-lists 3 | * Lazy and infinite linked lists for JavaScript. 4 | * 5 | * source/lib.js 6 | * 7 | * Core functions for lazy-linked-lists. 8 | * @license ISC 9 | */ 10 | 11 | import {List} from './list' 12 | 13 | import { 14 | LT, 15 | GT, 16 | EQ 17 | } from './ord' 18 | 19 | import { 20 | EmptyListError, 21 | OutOfRangeError 22 | } from './error' 23 | 24 | export const emptyList = new List() 25 | 26 | /** 27 | * Create a new `List` from a series of zero or more values. 28 | * @param {...*} as - Values to put into a new `List` 29 | * @returns {List} The new `List` 30 | * @kind function 31 | * @example 32 | * list(1,2,3); // => [1:2:3:[]] 33 | */ 34 | export const list = (...as) => as.length === 0 ? emptyList : new List(as.shift(), list(...as)) 35 | 36 | /** 37 | * Build a `List` from a range of values using lazy evaluation (i.e. each successive value is only 38 | * computed on demand, making infinite lists feasible). To supply your own function for determining 39 | * the increment, use `listRangeBy`. 40 | * @param {*} start - The starting value 41 | * @param {*} end - The end value 42 | * @returns {List} A `List` that will be evaluated lazily 43 | * @kind function 44 | */ 45 | export const listRange = (start, end) => { 46 | if (start === end) { return list(start) } 47 | if (start > end) { return listRangeBy(start, end, x => x - 1) } 48 | return listRangeBy(start, end, x => x + 1) 49 | } 50 | 51 | /** 52 | * Build a `List` from a range of values using lazy evaluation and incrementing it using a given 53 | * step function. 54 | * @param {*} start - The starting value 55 | * @param {*} end - The end value 56 | * @param {Function} step - The increment function 57 | * @returns {List} A `List` that will be evaluated lazily 58 | * @kind function 59 | */ 60 | export const listRangeBy = (start, end, step) => { 61 | if (start === end) { return list(start) } 62 | let x = start 63 | const xs = list(x) 64 | const listGenerator = function* () { 65 | x = step(x) 66 | while (start < end ? x < end : x > end) { 67 | yield list(x) 68 | x = step(x) 69 | } 70 | } 71 | const gen = listGenerator() 72 | const handler = { 73 | get: function (target, prop) { 74 | if (prop === `tail` && isEmpty(tail(target))) { 75 | const next = gen.next() 76 | if (next.done === false) { target[prop] = () => new Proxy(next.value, handler) } 77 | } 78 | return target[prop] 79 | } 80 | } 81 | const proxy = new Proxy(xs, handler) 82 | return proxy 83 | } 84 | 85 | /** 86 | * Append one `List` to another. 87 | * @param {List} xs - A `List` 88 | * @param {List} ys - A `List` 89 | * @returns {List} A new `List`, the result of appending `xs` to `ys` 90 | * @kind function 91 | * @example 92 | * const lst1 = list(1,2,3); 93 | * const lst2 = list(4,5,6); 94 | * listAppend(lst1, lst2); // => [1:2:3:4:5:6:[]] 95 | */ 96 | export const listAppend = (xs, ys) => { 97 | if (isEmpty(xs)) { return ys } 98 | if (isEmpty(ys)) { return xs } 99 | return cons(head(xs), listAppend(tail(xs), ys)) 100 | } 101 | 102 | /** 103 | * Create a new `List` from a head and tail. 104 | * @param {*} x - Any value, the head of the new list 105 | * @param {List} xs - A `List`, the tail of the new list 106 | * @returns {List} The new `List`, constructed from `x` and `xs` 107 | * @kind function 108 | * @example 109 | * const lst = list(4,5,6); 110 | * cons(3)(lst); // => [3:4:5:6:[]] 111 | */ 112 | export const cons = (x, xs) => new List(x, xs) 113 | 114 | /** 115 | * Extract the first element of a `List`. 116 | * @param {List} xs - A `List` 117 | * @returns {*} The head of the `List` 118 | * @kind function 119 | * @example 120 | * const lst = list(1,2,3); 121 | * head(lst); // => 1 122 | */ 123 | export const head = xs => { 124 | if (isEmpty(xs)) { throw new EmptyListError(head) } 125 | return xs.head() 126 | } 127 | 128 | /** 129 | * Extract the last element of a `List`. 130 | * @param {List} xs - A `List` 131 | * @returns {*} The last element of the `List` 132 | * @kind function 133 | * @example 134 | * const lst = list(1,2,3); 135 | * last(lst); // => 3 136 | */ 137 | export const last = xs => { 138 | if (isEmpty(xs)) { throw new EmptyListError(last) } 139 | return isEmpty(tail(xs)) ? head(xs) : last(tail(xs)) 140 | } 141 | 142 | /** 143 | * Extract the elements after the head of a `List`. 144 | * @param {List} xs - A `List` 145 | * @returns {List} The tail of the `List` 146 | * @kind function 147 | * @example 148 | * const lst = list(1,2,3); 149 | * tail(lst); // => [2:3:[]] 150 | */ 151 | export const tail = xs => { 152 | if (isEmpty(xs)) { throw new EmptyListError(tail) } 153 | return xs.tail() 154 | } 155 | 156 | /** 157 | * Return all the elements of a `List` except the last one. 158 | * @param {List} xs - A `List` 159 | * @returns {List} A new `List`, without the original list's last element 160 | * @kind function 161 | * @example 162 | * const lst = list(1,2,3); 163 | * init(lst); // => [1:2:[]] 164 | */ 165 | export const init = xs => { 166 | if (isEmpty(xs)) { throw new EmptyListError(init) } 167 | return isEmpty(tail(xs)) ? emptyList : cons(head(xs), init(tail(xs))) 168 | } 169 | 170 | /** 171 | * Return the length of a `List`. 172 | * @param {List} xs - A `List` 173 | * @returns {number} The length of the `List` 174 | * @kind function 175 | * @example 176 | * const lst = list(1,2,3); 177 | * length(lst); // => 3 178 | */ 179 | export const length = xs => { 180 | const lenAcc = (xs, n) => isEmpty(xs) ? n : lenAcc(tail(xs), n + 1) 181 | return lenAcc(xs, 0) 182 | } 183 | 184 | /** 185 | * Determine whether a given object is a `List`. 186 | * @param {*} a - Any object 187 | * @returns {boolean} `true` if the object is a `List` and `false` otherwise 188 | * @kind function 189 | */ 190 | export const isList = a => a instanceof List 191 | 192 | /** 193 | * Check whether a `List` is empty. Returns `true` if the `List` is empty or false if it is 194 | * non-empty. 195 | * @param {*} xs - A `List` 196 | * @returns {boolean} `true` if the `List` is empty, `false` if it is non-empty 197 | * @kind function 198 | * @example 199 | * isEmpty([]); // => true 200 | * isEmpty([[]]); // => false 201 | * isEmpty(emptyList); // => true 202 | * isEmpty(list(emptyList)); // => false 203 | */ 204 | export const isEmpty = xs => xs === emptyList 205 | 206 | /** 207 | * Convert an array into a `List`. 208 | * @param {Array.<*>} arr - An array to convert into a `List` 209 | * @returns {List} A new `List`, the converted array 210 | * @kind function 211 | * @example 212 | * const arr = [1,2,3]; 213 | * fromArrayToList(arr); // => [1:2:3:[]] 214 | */ 215 | export const fromArrayToList = arr => list(...arr) 216 | 217 | /** 218 | * Convert a `List` into an array. 219 | * @param {List} xs - A `List` to convert into an array 220 | * @returns {Array} A new array, the converted `List` 221 | * @kind function 222 | * @example 223 | * const lst = list(1,2,3); 224 | * fromListToArray(lst); // => [1,2,3] 225 | */ 226 | export const fromListToArray = xs => isEmpty(xs) ? [] : [head(xs)].concat(fromListToArray(tail(xs))) 227 | /** 228 | * Convert a `List` into a string. 229 | * @param {List} xs - A `List` to convert into a string 230 | * @returns {string} A new string, the converted `List` 231 | * @kind function 232 | * @example 233 | * const str = list('a','b','c'); 234 | * fromListToString(str); // => "abc" 235 | */ 236 | export const fromListToString = xs => fromListToArray(xs).join(``) 237 | 238 | /** 239 | * Convert a string into a `List`. 240 | * @param {string} str - A string to convert into a `List` 241 | * @returns {List} A new `List`, the converted string 242 | * @kind function 243 | * @example 244 | * const str = `abc`; 245 | * fromStringToList(str); // => [abc] 246 | */ 247 | export const fromStringToList = str => fromArrayToList(str.split(``)) 248 | 249 | /** 250 | * Concatenate the elements in a `List` of lists. 251 | * @param {List} xss - A `List` of lists 252 | * @returns {List} The concatenated `List` 253 | * @kind function 254 | * @example 255 | * const lst1 = list(1,2,3); 256 | * const lst2 = list(4,5,6); 257 | * const lst3 = list(7,8,9); 258 | * const xss = list(lst1, lst2, lst3); // [[1:2:3:[]]:[4:5:6:[]]:[7:8:9:[]]:[]] 259 | * concat(xss); // => [1:2:3:4:5:6:7:8:9:[]] 260 | */ 261 | export const concat = xss => { 262 | if (isEmpty(xss)) { return emptyList } 263 | const x = head(xss) 264 | const xs = tail(xss) 265 | return listAppend(x, concat(xs)) 266 | } 267 | 268 | /** 269 | * Return the value from a `List` at the specified index, starting at 0. 270 | * @param {List} as - The `List` to index into 271 | * @param {number} n - The index to return 272 | * @returns {*} The value at the specified index 273 | * @kind function 274 | * @example 275 | * const lst = list(1,2,3,4,5); 276 | * index(lst, 3)); // => 4 277 | */ 278 | export const index = (as, n) => { 279 | if (n < 0 || isEmpty(as)) { throw new OutOfRangeError(head) } 280 | const x = head(as) 281 | const xs = tail(as) 282 | if (n === 0) { return x } 283 | return index(xs, n - 1) 284 | } 285 | 286 | /** 287 | * Return the `List` of elements in a `List` for which a function `f` returns `true`. 288 | * @param {Function} f - The filter function. Must return a `boolean` 289 | * @param {List} as - The `List` to filter 290 | * @returns {List} The filtered `List` 291 | * @kind function 292 | * @example 293 | * const lst = listRange(1,50); 294 | * const f = x => and(odd(x), greaterThan(x, 10)); 295 | * filter(f, lst); // => [11:13:15:17:19:21:23:25:27:29:31:33:35:37:39:41:43:45:47:49:[]] 296 | */ 297 | export const filter = (f, as) => { 298 | if (isEmpty(as)) { return emptyList } 299 | const x = head(as) 300 | const xs = tail(as) 301 | if (f(x) === true) { return cons(x, filter(f, xs)) } 302 | return filter(f, xs) 303 | } 304 | 305 | /** 306 | * Map a function over a `List` and put the results into a new `List`. 307 | * @param {Function} f - The function to map 308 | * @param {List} as - The `List` to map over 309 | * @returns {List} A `List` of results 310 | * @kind function 311 | * @example 312 | * const lst = list(1,2,3,4,5); 313 | * const f = x => x * 3; 314 | * map(f, lst)); // => [3:6:9:12:15:[]] 315 | */ 316 | export const map = (f, as) => { 317 | if (isEmpty(as)) { return emptyList } 318 | const x = f(head(as)) 319 | const xs = tail(as) 320 | return cons(x, map(f, xs)) 321 | } 322 | 323 | /** 324 | * Reverse the elements of a `List` and return them in a new list. 325 | * @param {List} xs - A `List` 326 | * @returns {List} The reversed `List` 327 | * @kind function 328 | * @example 329 | * const lst = list(1,2,3,4,5); 330 | * reverse(lst); // => [5:4:3:2:1:[]] 331 | */ 332 | export const reverse = xs => { 333 | const r = (xs, a) => isEmpty(xs) ? a : r(tail(xs), cons(head(xs), a)) 334 | return r(xs, emptyList) 335 | } 336 | 337 | /** 338 | * Sort a list using regular value comparison. Use `sortBy` to supply your own comparison 339 | * function. Uses a merge sort algorithm. 340 | * @param {List} xs - The `List` to sort 341 | * @returns {List} - The sorted `List` (the original list is unmodified) 342 | * @kind function 343 | * @example 344 | * const lst1 = list(20,19,18,17,16,15,14,13,12,11,10,1,2,3,4,5,6,7,8,9); 345 | * sort(lst1); // => [1:2:3:4:5:6:7:8:9:10:11:12:13:14:15:16:17:18:19:20:[]] 346 | * const f = x => x + 1; 347 | * const lst2 = reverse(listRange(1, 11, f)); // [10:9:8:7:6:5:4:3:2:1:[]] 348 | * sort(lst2); // => [1:2:3:4:5:6:7:8:9:10:[]] 349 | */ 350 | export const sort = xs => sortBy((a, b) => a === b ? EQ : (a < b ? LT : GT), xs) 351 | 352 | /** 353 | * Sort a list using a comparison function of your choice. Uses a merge sort algorithm. 354 | * @param {Function} cmp - The comparison function—must return an `Ordering` 355 | * @param {List} as - The `List` to sort 356 | * @returns {List} The sorted `List` (the original list is unmodified) 357 | * @kind function 358 | */ 359 | export const sortBy = (cmp, as) => { 360 | const sequences = as => { 361 | if (isEmpty(as)) { return list(as) } 362 | let xs = tail(as) 363 | if (isEmpty(xs)) { return list(as) } 364 | const a = head(as) 365 | const b = head(xs) 366 | xs = tail(xs) 367 | if (cmp(a, b) === GT) { return descending(b, list(a), xs) } 368 | return ascending(b, cons.bind(null, a), xs) 369 | } 370 | const descending = (a, as, bbs) => { 371 | if (isEmpty(bbs)) { return cons(cons(a, as), sequences(bbs)) } 372 | const b = head(bbs) 373 | const bs = tail(bbs) 374 | if (cmp(a, b) === GT) { return descending(b, cons(a, as), bs) } 375 | return cons(cons(a, as), sequences(bbs)) 376 | } 377 | const ascending = (a, as, bbs) => { 378 | if (isEmpty(bbs)) { return cons(as(list(a)), sequences(bbs)) } 379 | const b = head(bbs) 380 | const bs = tail(bbs) 381 | const ys = ys => as(cons(a, ys)) 382 | if (cmp(a, b) !== GT) { return ascending(b, ys, bs) } 383 | return cons(as(list(a)), sequences(bbs)) 384 | } 385 | const mergeAll = xs => { 386 | if (isEmpty(tail(xs))) { return head(xs) } 387 | return mergeAll(mergePairs(xs)) 388 | } 389 | const mergePairs = as => { 390 | if (isEmpty(as)) { return as } 391 | let xs = tail(as) 392 | if (isEmpty(xs)) { return as } 393 | const a = head(as) 394 | const b = head(xs) 395 | xs = tail(xs) 396 | return cons(merge(a, b), mergePairs(xs)) 397 | } 398 | const merge = (as, bs) => { 399 | if (isEmpty(as)) { return bs } 400 | if (isEmpty(bs)) { return as } 401 | const a = head(as) 402 | const as1 = tail(as) 403 | const b = head(bs) 404 | const bs1 = tail(bs) 405 | if (cmp(a, b) === GT) { return cons(b, merge(as, bs1)) } 406 | return cons(a, merge(as1, bs)) 407 | } 408 | return mergeAll(sequences(as)) 409 | } 410 | 411 | /** 412 | * Return the prefix of a `List` of a given length. 413 | * @param {number} n - The length of the prefix to take 414 | * @param {List} as - The `List` to take from 415 | * @returns {List} A new `List`, the desired prefix of the original list 416 | * @kind function 417 | * @example 418 | * const lst = list(1,2,3); 419 | * take(2, lst); // => [1:2:[]] 420 | */ 421 | export const take = (n, as) => { 422 | if (n <= 0) { return emptyList } 423 | if (isEmpty(as)) { return emptyList } 424 | const x = head(as) 425 | const xs = tail(as) 426 | return cons(x, take(n - 1, xs)) 427 | } 428 | 429 | /** 430 | * Return the suffix of a `List` after discarding a specified number of values. 431 | * @param {number} n - The number of values to drop 432 | * @param {List} as - The `List` to drop values from 433 | * @returns {List} A new `List`, the desired suffix of the original list 434 | * @kind function 435 | * @example 436 | * const lst = list(1,2,3); 437 | * drop(2, lst); // => [3:[]] 438 | */ 439 | export const drop = (n, as) => { 440 | if (n <= 0) { return as } 441 | if (isEmpty(as)) { return emptyList } 442 | const xs = tail(as) 443 | return drop(n - 1, xs) 444 | } 445 | 446 | /** 447 | * Return the longest prefix (possibly empty) of a `List` of values that satisfy a predicate 448 | * function. 449 | * @param {Function} p - The predicate function (should return `boolean`) 450 | * @param {List} as - The `List` to take from 451 | * @returns {List} The `List` of values that satisfy the predicate function 452 | * @kind function 453 | * @example 454 | * const lst = list(1,2,3,4,1,2,3,4); 455 | * const f = x => x < 3; 456 | * takeWhile(f, lst); // => [1:2:[]] 457 | */ 458 | export const takeWhile = (p, as) => { 459 | if (isEmpty(as)) { return emptyList } 460 | const x = head(as) 461 | const xs = tail(as) 462 | const test = p(x) 463 | if (test === true) { return cons(x, takeWhile(p, xs)) } 464 | return emptyList 465 | } 466 | 467 | /** 468 | * Drop values from a `List` while a given predicate function returns `true` for each value. 469 | * @param {Function} p - The predicate function (should return `boolean`) 470 | * @param {List} as - The `List` to drop values from 471 | * @returns {List} The `List` of values that do not satisfy the predicate function 472 | * @kind function 473 | * @example 474 | * const lst = list(1,2,3,4,5,1,2,3); 475 | * const f = x => x < 3; 476 | * dropWhile(f, lst); // => [3:4:5:1:2:3:[]] 477 | */ 478 | export const dropWhile = (p, as) => { 479 | if (isEmpty(as)) { return emptyList } 480 | const x = head(as) 481 | const xs = tail(as) 482 | const test = p(x) 483 | if (test === true) { return dropWhile(p, xs) } 484 | return as 485 | } 486 | 487 | /** 488 | * Generate an infinite `List`. Use `listInfBy` to supply your own step function. 489 | * @param {*} start - The value with which to start the `List` 490 | * @returns {List} An infinite `List` of consecutive values, incremented from `start` 491 | * @kind function 492 | */ 493 | export const listInf = start => listInfBy(start, x => x + 1) 494 | 495 | /** 496 | * Generate an infinite `List`, incremented using a given step function. 497 | * @param {*} start - The value with which to start the `List` 498 | * @param {Function} step - A unary step function 499 | * @returns {List} An infinite `List` of consecutive values, incremented from `start` 500 | * @kind function 501 | */ 502 | export const listInfBy = (start, step) => listRangeBy(start, Infinity, step) 503 | 504 | /** 505 | * Return an infinite `List` of repeated applications of a function to a value. 506 | * @param {Function} f - The function to apply 507 | * @param {*} x - The value to apply the function to 508 | * @returns {List} An infinite `List` of repeated applications of `f` to `x` 509 | * @kind function 510 | * @example 511 | * const f = x => x * 2; 512 | * const lst = iterate(f, 1); 513 | * take(10, lst); // => [1:2:4:8:16:32:64:128:256:512:[]] 514 | * index(lst, 10); // => 1024 515 | */ 516 | export const iterate = (f, x) => listInfBy(x, x => f(x)) 517 | 518 | /** 519 | * Build an infinite `List` of identical values. 520 | * @param {*} a - The value to repeat 521 | * @returns {List} The infinite `List` of repeated values 522 | * @kind function 523 | * @example 524 | * const lst = repeat(3); 525 | * take(10, lst); // => [3:3:3:3:3:3:3:3:3:3:[]] 526 | * index(lst, 100); // => 3 527 | */ 528 | export const repeat = a => cons(a, listInfBy(a, a => a)) 529 | 530 | /** 531 | * Return a `List` of a specified length in which every value is the same. 532 | * @param {number} n - The length of the `List` 533 | * @param {*} x - The value to replicate 534 | * @returns {List} The `List` of values 535 | * @kind function 536 | * @example 537 | * replicate(10, 3); // => [3:3:3:3:3:3:3:3:3:3:[]] 538 | */ 539 | export const replicate = (n, x) => take(n, repeat(x)) 540 | 541 | /** 542 | * Return the infinite repetition of a `List` (i.e. the "identity" of infinite lists). 543 | * @param {List} as - A finite `List` 544 | * @returns {List} A circular `List`, the original `List` infinitely repeated 545 | * @kind function 546 | * @example 547 | * const lst = list(1,2,3); 548 | * const c = cycle(lst); 549 | * take(9, c); // => [1:2:3:1:2:3:1:2:3:[]] 550 | * index(c, 100); // => 2 551 | */ 552 | export const cycle = as => { 553 | if (isEmpty(as)) { throw new EmptyListError(cycle) } 554 | let x = head(as) 555 | let xs = tail(as) 556 | const c = list(x) 557 | /* eslint no-constant-condition: ["error", { "checkLoops": false }] */ 558 | const listGenerator = function* () { 559 | do { 560 | x = isEmpty(xs) ? head(as) : head(xs) 561 | xs = isEmpty(xs) ? tail(as) : tail(xs) 562 | yield list(x) 563 | } while (true) 564 | } 565 | const gen = listGenerator() 566 | const handler = { 567 | get: function (target, prop) { 568 | if (prop === `tail` && isEmpty(tail(target))) { 569 | const next = gen.next() 570 | target[prop] = () => new Proxy(next.value, handler) 571 | } 572 | return target[prop] 573 | } 574 | } 575 | const proxy = new Proxy(c, handler) 576 | return proxy 577 | } 578 | -------------------------------------------------------------------------------- /source/list.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lazy-linked-lists 3 | * Lazy and infinite linked lists for JavaScript. 4 | * 5 | * source/list.js 6 | * 7 | * List data type. 8 | * @license ISC 9 | */ 10 | 11 | import { 12 | LT, 13 | GT, 14 | EQ 15 | } from './ord' 16 | 17 | import { 18 | emptyList, 19 | list, 20 | cons, 21 | head, 22 | tail, 23 | listAppend, 24 | isEmpty, 25 | fromListToArray, 26 | fromListToString, 27 | map, 28 | concat 29 | } from './lib' 30 | 31 | /** 32 | * A data constructor for a `List`. 33 | * @alias module:list.List 34 | * @kind class 35 | * @extends Type 36 | * @private 37 | */ 38 | export class List { 39 | /** 40 | * Create a new `List`. 41 | * @param {*} head - The value to put at the head of the `List` 42 | * @param {List} tail - The tail of the `List`, which is also a `List` (possibly the empty list) 43 | * @private 44 | */ 45 | constructor (x, xs) { 46 | this.head = null 47 | this.tail = null 48 | this.head = () => x 49 | this.tail = () => xs 50 | } 51 | [Symbol.iterator] () { 52 | const listIterator = function* (xs) { 53 | do { 54 | yield head(xs) 55 | xs = tail(xs) 56 | } while (xs !== emptyList) 57 | } 58 | const gen = listIterator(this) 59 | return { 60 | next () { return gen.next() } 61 | } 62 | } 63 | /** 64 | * Determine whether the `List` on which it is called is exactly equal to another `List`. 65 | * @param {List} ys - The other `List` on which to perform the equality check 66 | * @returns {boolean} `true` if this `List` === `ys` and `false` otherwise 67 | */ 68 | isEq (ys) { return fromListToArray(this).every((x, i) => x === fromListToArray(ys)[i]) } 69 | /** 70 | * Compare the `List` on which it is called to another `List` to determine its relative "ordering" 71 | * (less than, greater than, or equal). 72 | * @param {List} ys - The other `List` to compare 73 | * @returns {Symbol} `LT`, `GT`, or `EQ`, the ordering of this `List` as compared to `ys` 74 | */ 75 | compare (ys) { 76 | if (isEmpty(this) && isEmpty(ys)) { return EQ } 77 | if (isEmpty(this) && !isEmpty(ys)) { return LT } 78 | if (!isEmpty(this) && isEmpty(ys)) { return GT } 79 | if (this.head() === head(ys)) { return this.tail().compare(tail(ys)) } 80 | return this.head() < head(ys) ? LT : GT 81 | } 82 | /** 83 | * Determine whether the `List` on which it is called is less than another `List`. 84 | * @param {List} ys - The other `List` to compare 85 | * @returns {boolean} `true` if this `List` < `ys` and `false` otherwise 86 | */ 87 | isLessThan (ys) { return this.compare(ys) === LT } 88 | /** 89 | * Determine whether the `List` on which it is called is less than or equal to another `List`. 90 | * @param {List} ys - The other `List` to compare 91 | * @returns {boolean} `true` if this `List` <= `ys` and `false` otherwise 92 | */ 93 | isLessThanOrEqual (ys) { return this.compare(ys) !== GT } 94 | /** 95 | * Determine whether the `List` on which it is called is greater than another `List`. 96 | * @param {List} ys - The other `List` to compare 97 | * @returns {boolean} `true` if this `List` > `ys` and `false` otherwise 98 | */ 99 | isGreaterThan (ys) { return this.compare(ys) === GT } 100 | /** 101 | * Determine whether the `List` on which it is called is greater than or equal to another `List`. 102 | * @param {List} ys - The other `List` to compare 103 | * @returns {boolean} `true` if this `List` >= `ys` and `false` otherwise 104 | */ 105 | isGreaterThanOrEqual (ys) { return this.compare(ys) !== LT } 106 | /** 107 | * Return the monoidal identity of a `List`, which is the empty list. 108 | * @returns {List} The empty list 109 | */ 110 | mempty () { return emptyList } 111 | /** 112 | * Perform an associative operation on the `List` on which it is called and another `List`. This 113 | * is a monoidal operation, which for this data type is equivalent to `listAppend`. 114 | * @param {List} ys - The other `List` 115 | * @returns {List} A new `List`, the result of the associative operation 116 | */ 117 | mappend (ys) { return listAppend(this, ys) } 118 | /** 119 | * Apply a function to each value of the `List` on which it is called and accumulate the result. 120 | * @param {Function} f - The function to fold over the `List` 121 | * @param {*} acc - The starting accumulator value 122 | * @returns {*} The result of folding `f` over the `List` 123 | */ 124 | foldr (f, acc) { 125 | if (isEmpty(this)) { return acc } 126 | const x = this.head() 127 | const xs = this.tail() 128 | return f(x, xs.foldr(f, acc)) 129 | } 130 | /** 131 | * Map each element of a `List` to a function, evaluate the functions from left to right, and 132 | * collect the results. 133 | * @param {Function} f - The function to map over the `List` 134 | * @returns {*} The result of the traversal 135 | */ 136 | traverse (f) { 137 | const consf = (x, ys) => f(x).fmap(a => cons.bind(null, a)).ap(ys) 138 | return isEmpty(this) ? this.pure(emptyList) : this.foldr(consf, this.pure(emptyList)) 139 | } 140 | /** 141 | * Map a function over each element of a `List`, and return the results in a new `List`. 142 | * @param {Function} f - The function to map over the `List` 143 | * @returns {List} A new `List`, the result of the mapping 144 | */ 145 | fmap (f) { return map(f, this) } 146 | /** 147 | * Lift a value into the context of a `List`. 148 | * @param {*} a - The value to lift into a `List` 149 | * @returns {List} The lifted value 150 | */ 151 | pure (a) { return list(a) } 152 | /** 153 | * Apply the `List` of functions on which it is called to a `List` of values, and return the 154 | * results in a new `List`. 155 | * @param {List} ys - The `List` of values 156 | * @returns {List} A new `List`, the result of the applications 157 | */ 158 | ap (ys) { return isEmpty(this) ? emptyList : listAppend(ys.fmap(this.head()), this.tail().ap(ys)) } 159 | /** 160 | * Perform a monadic "bind" operation. Apply a function that returns a `List` to the values of the 161 | * `List` on which it is called and flatten the result to produce a new `List`. 162 | * @param {Function} f - The function to apply to the values of this `List` 163 | * @returns {List} A new `List`, the result of the monadic operation 164 | */ 165 | flatMap (f) { return concat(map(f, this)) } 166 | /** 167 | * Perform a monadic chaining operation from one `List` to another, discarding the value of the 168 | * `List` on which it is called. 169 | * @param {List} ys - The function to chain 170 | * @returns {List} A new `List`, the result of the chaining operation 171 | */ 172 | then (ys) { 173 | const c = (a, b) => (b, a) // eslint-disable-line 174 | const id = a => a 175 | return this.fmap(c.bind(null, id)).ap(ys) 176 | } 177 | toString () { return `[Object List]` } 178 | typeOf () { return `[${isEmpty(this) ? '' : typeof this.head()}]` } 179 | valueOf () { 180 | if (this === emptyList) { return `[]` } 181 | const value = xs => isEmpty(xs) ? `[]` : `${head(xs)}:${value(tail(xs))}` 182 | return `[${this.typeOf() === `[string]` ? fromListToString(this) : value(this)}]` 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /source/ord.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lazy-linked-lists 3 | * Lazy and infinite linked lists for JavaScript. 4 | * 5 | * source/ord.js 6 | * 7 | * Ordering symbols. 8 | * @license ISC 9 | */ 10 | 11 | /** 12 | * The "less than" ordering. Equivalent to <. 13 | * @const {Symbol} LT 14 | */ 15 | export const LT = Symbol() 16 | 17 | /** 18 | * The "greater than" ordering. Equivalent to >. 19 | * @const {Symbol} GT 20 | */ 21 | export const GT = Symbol() 22 | 23 | /** 24 | * The "equals" ordering. Equivalent to ===. 25 | * @const {Symbol} EQ 26 | */ 27 | export const EQ = Symbol() 28 | -------------------------------------------------------------------------------- /test/concat-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`concat()`, function () { 4 | const lst1 = lazy.list(1, 2, 3) 5 | const lst2 = lazy.list(4, 5, 6) 6 | const lst3 = lazy.list() 7 | const xss = lazy.list(lst1, lst2, lst3) 8 | it(`should concatenate (flatten) the elements in a list of lists`, function () { 9 | lazy.concat(xss).isEq(lazy.list(1, 2, 3, 4, 5, 6)).should.be.true 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/cons-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`cons()`, function () { 4 | const lst = lazy.list(4, 5, 6) 5 | it(`should create a new list from a head and tail`, function () { 6 | lazy.cons(3, lst).isEq(lazy.list(3, 4, 5, 6)) 7 | lazy.cons(1, lazy.emptyList).isEq(lazy.list(1)).should.be.true 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/cycle-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`cycle()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | const cyc = lazy.cycle(lst) 6 | it(`should return the infinite repetition of a list`, function () { 7 | lazy.take(9, cyc).isEq(lazy.list(1, 2, 3, 1, 2, 3, 1, 2, 3)).should.be.true 8 | lazy.index(cyc, 100).should.equal(2) 9 | }) 10 | it(`should throw an error if the list is empty`, function () { 11 | lazy.cycle.bind(null, lazy.emptyList).should.throw() 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /test/drop-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`drop()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | it(`should return the suffix of a list after discarding a specified number of values`, function () { 6 | lazy.drop(2, lst).isEq(lazy.list(3)).should.be.true 7 | }) 8 | it(`should return the empty list if the second argument is the empty list`, function () { 9 | lazy.drop(2, lazy.list()).isEq(lazy.emptyList).should.be.true 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/dropWhile-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`dropWhile()`, function () { 4 | const lst = lazy.list(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 5 | const f = x => x < 3 6 | it(`should drop values from a list while a given predicate function returns true`, function () { 7 | lazy.dropWhile(f, lst).isEq(lazy.list(3, 4, 5, 6, 7, 8, 9, 10)).should.be.true 8 | }) 9 | it(`should return an empty list if the second argument is an empty list`, function () { 10 | lazy.dropWhile(f, lazy.emptyList).isEq(lazy.emptyList).should.be.true 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /test/emptyList-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`emptyList`, function () { 4 | const lst = lazy.list() 5 | it(`should be an empty list`, function () { 6 | lst.should.equal(lazy.emptyList) 7 | lazy.emptyList.should.equal(lst) 8 | lazy.length(lazy.emptyList).should.equal(0) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/filter-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`filter()`, function () { 4 | const lst = lazy.listRangeBy(0, 50, x => x + 5) 5 | const f = x => x % 10 === 0 6 | it(`should return the list of elements in a list for which a function f returns true`, function () { 7 | lazy.filter(f, lst).isEq(lazy.list(0, 10, 20, 30, 40, 50)).should.be.true 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/fromArrayToList-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`fromArrayToList()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | const arr = [1, 2, 3] 6 | it(`should convert an array into a list`, function () { 7 | lazy.fromArrayToList(arr).isEq(lst).should.be.true 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/fromListToArray-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`fromListToArray()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | const arr = [1, 2, 3] 6 | it(`should convert a list into an array`, function () { 7 | lazy.fromListToArray(lst).should.eql(arr) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/fromListToString-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`fromListToString()`, function () { 4 | const str = lazy.list(`a`, `b`, `c`) 5 | it(`should convert a list into a string`, function () { 6 | lazy.fromListToString(str).should.equal(`abc`) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /test/fromStringToList-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`fromStringToList()`, function () { 4 | const str = lazy.list(`a`, `b`, `c`) 5 | it(`should convert a string into a list`, function () { 6 | lazy.fromStringToList(`abc`).isEq(str).should.be.true 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /test/global.js: -------------------------------------------------------------------------------- 1 | /** 2 | * lazy-linked-lists 3 | * Lazy and infinite linked lists for JavaScript. 4 | * 5 | * global.js 6 | * 7 | * Global scope for unit tests. 8 | * @license ISC 9 | */ 10 | 11 | /* global describe, it */ 12 | 13 | import 'should' 14 | -------------------------------------------------------------------------------- /test/head-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`head()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | it(`should extract the first element of a list`, function () { 6 | lazy.head(lst).should.equal(1) 7 | }) 8 | it(`should throw an error if the list is empty`, function () { 9 | lazy.head.bind(null, lazy.emptyList).should.throw() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/index-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`index()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | it(`should return the value from a list at the specified index, starting at 0`, function () { 6 | lazy.index(lst, 2).should.equal(3) 7 | }) 8 | it(`should throw an error if the index value specified is less than 0`, function () { 9 | lazy.index.bind(null, lst, -1).should.throw() 10 | }) 11 | it(`should throw an error if the index value specified is greater than or equal to the length of the list`, function () { 12 | lazy.index.bind(null, lst, 3).should.throw() 13 | }) 14 | }) 15 | -------------------------------------------------------------------------------- /test/init-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`init()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | it(`should return all the elements of a list except the last one`, function () { 6 | lazy.init(lst).isEq(lazy.list(1, 2)).should.be.true 7 | }) 8 | it(`should throw an error if the list is empty`, function () { 9 | lazy.init.bind(null, lazy.emptyList).should.throw() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/isEmpty-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`isEmpty()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | it(`should return true if the argument is an empty list`, function () { 6 | lazy.isEmpty(lazy.emptyList).should.be.true 7 | }) 8 | it(`should return false if the argument list is not empty`, function () { 9 | lazy.isEmpty(lst).should.be.false 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/isList-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`isList()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | it(`should return true if the argument is a list`, function () { 6 | lazy.isList(lst).should.be.true 7 | lazy.isList(lazy.emptyList).should.be.true 8 | }) 9 | it(`should return false if the argument is not a list`, function () { 10 | lazy.isList(0).should.be.false 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /test/iterate-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`iterate()`, function () { 4 | const inf = lazy.iterate(x => x * 2, 1) 5 | const lst = lazy.list(1, 2, 4, 8, 16, 32, 64, 128, 256, 512) 6 | it(`should return an infinite list of repeated applications of a function to a value`, function () { 7 | lazy.take(10, inf).isEq(lst).should.be.true 8 | lazy.index(inf, 10).should.equal(1024) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/last-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`last()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | it(`should extract the last element of a list`, function () { 6 | lazy.last(lst).should.equal(3) 7 | }) 8 | it(`should throw an error if the list is empty`, function () { 9 | lazy.last.bind(null, lazy.emptyList).should.throw() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/length-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`length()`, function () { 4 | const lst1 = lazy.list(1, 2, 3) 5 | const lst2 = lazy.listRange(0, 10) 6 | it(`should return the length of a list`, function () { 7 | lazy.length(lst1).should.equal(3) 8 | lazy.length(lst2).should.equal(10) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/list-class-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | const LT = lazy.LT 4 | const GT = lazy.GT 5 | const EQ = lazy.EQ 6 | 7 | describe(`List`, function () { 8 | describe(`#[Symbol.iterator]()`, function () { 9 | const lst = lazy.list(1, 2, 3) 10 | it(`should be an iterable`, function () { 11 | let i = 1 12 | for (let value of lst) { 13 | value.should.equal(i) 14 | i += 1 15 | } 16 | }) 17 | }) 18 | describe(`#isEq()`, function () { 19 | const lst1 = lazy.list(1, 2, 3) 20 | const lst2 = lazy.list(4, 5, 6) 21 | it(`should compare two lists for equality`, function () { 22 | lst1.isEq(lst1).should.be.true 23 | lst1.isEq(lst2).should.be.false 24 | }) 25 | }) 26 | describe(`#compare()`, function () { 27 | const lst1 = lazy.list(1, 2, 3) 28 | const lst2 = lazy.list(4, 5, 6) 29 | it(`should compare two lists and return an ordering value`, function () { 30 | lazy.emptyList.compare(lazy.emptyList).should.equal(EQ) 31 | lazy.emptyList.compare(lst1).should.equal(LT) 32 | lst1.compare(lazy.emptyList).should.equal(GT) 33 | lst1.compare(lst2).should.equal(LT) 34 | lst2.compare(lst1).should.equal(GT) 35 | lst1.compare(lst1).should.equal(EQ) 36 | }) 37 | }) 38 | describe(`#isLessThan()`, function () { 39 | const lst1 = lazy.list(1, 2, 3) 40 | const lst2 = lazy.list(4, 5, 6) 41 | it(`should return true if the first list is less than the second`, function () { 42 | lst1.isLessThan(lst2).should.be.true 43 | }) 44 | it(`should return false if the first list is not less than the second`, function () { 45 | lst1.isLessThan(lst1).should.be.false 46 | lst2.isLessThan(lst1).should.be.false 47 | }) 48 | }) 49 | describe(`#isLessThanOrEqual()`, function () { 50 | const lst1 = lazy.list(1, 2, 3) 51 | const lst2 = lazy.list(4, 5, 6) 52 | it(`should return true if the first list is less than or equal to the second`, function () { 53 | lst1.isLessThanOrEqual(lst2).should.be.true 54 | lst1.isLessThanOrEqual(lst1).should.be.true 55 | }) 56 | it(`should return false if the first list is not less than or equal to the second`, function () { 57 | lst2.isLessThanOrEqual(lst1).should.be.false 58 | }) 59 | }) 60 | describe(`#isGreaterThan()`, function () { 61 | const lst1 = lazy.list(1, 2, 3) 62 | const lst2 = lazy.list(4, 5, 6) 63 | it(`should return true if the first list is greater than the second`, function () { 64 | lst2.isGreaterThan(lst1).should.be.true 65 | }) 66 | it(`should return false if the first list is not greater than the second`, function () { 67 | lst1.isGreaterThan(lst2).should.be.false 68 | lst2.isGreaterThan(lst2).should.be.false 69 | }) 70 | }) 71 | describe(`#isGreaterThanOrEqual()`, function () { 72 | const lst1 = lazy.list(1, 2, 3) 73 | const lst2 = lazy.list(4, 5, 6) 74 | it(`should return true if the first list is greater than or equal to the second`, function () { 75 | lst2.isGreaterThanOrEqual(lst1).should.be.true 76 | lst2.isGreaterThanOrEqual(lst2).should.be.true 77 | }) 78 | it(`should return false if the first list is not greater than or equal to the second`, function () { 79 | lst1.isGreaterThanOrEqual(lst2).should.be.false 80 | }) 81 | }) 82 | describe(`#mempty()`, function () { 83 | const lst1 = lazy.list(1, 2, 3) 84 | it(`should return an empty list (the identity of a list)`, function () { 85 | lst1.mempty().should.equal(lazy.emptyList) 86 | lazy.emptyList.mempty().should.equal(lazy.emptyList) 87 | }) 88 | }) 89 | describe(`#mappend()`, function () { 90 | const lst1 = lazy.list(1, 2, 3) 91 | const lst2 = lazy.list(4, 5, 6) 92 | it(`should append the first list to the second, satisfying the monoid laws`, function () { 93 | lst1.mappend(lazy.emptyList).isEq(lst1).should.be.true 94 | lazy.emptyList.mappend(lst1).isEq(lst1).should.be.true 95 | lst1.mappend(lst2).isEq(lazy.list(1, 2, 3, 4, 5, 6)).should.be.true 96 | }) 97 | }) 98 | describe(`#foldr()`, function () { 99 | const lst = lazy.list(1, 2, 3) 100 | const f = (x, y) => x * y 101 | it(`should fold a function over a list and accumulate the value that results`, function () { 102 | lst.foldr(f, 1).should.equal(6) 103 | lazy.reverse(lst).foldr(f, 1).should.equal(6) 104 | }) 105 | }) 106 | describe(`#traverse()`, function () { 107 | const lst = lazy.list(1, 2, 3) 108 | const f = x => lazy.list(x * 10) 109 | it(`should map a function that returns a list over a list and collect the results`, function () { 110 | lst.traverse(f).isEq(lazy.list(10, 20, 30)).should.be.true 111 | lazy.emptyList.traverse(f).isEq(lazy.list(lazy.emptyList)).should.be.true 112 | }) 113 | }) 114 | describe(`#fmap()`, function () { 115 | const lst = lazy.list(1, 2, 3) 116 | const f = x => x * 10 117 | it(`should map a function over a list and return a new list containing the results`, function () { 118 | lst.fmap(f).isEq(lazy.list(10, 20, 30)).should.be.true 119 | }) 120 | }) 121 | describe(`#pure()`, function () { 122 | const lst = lazy.list(1, 2, 3) 123 | it(`should return a new list containing the argument value`, function () { 124 | lst.pure(1).isEq(lazy.list(1)).should.be.true 125 | lst.pure(lst).isEq(lazy.list(lst)).should.be.true 126 | lazy.emptyList.pure(lazy.emptyList).isEq(lazy.list(lazy.list())).should.be.true 127 | }) 128 | }) 129 | describe(`#ap()`, function () { 130 | const lst = lazy.list(1, 2, 3) 131 | const f = x => x * 10 132 | const fs1 = lazy.list(f) 133 | const fs2 = lazy.list(f, f, f) 134 | it(`should sequentially apply the function(s) contained in a list to another list of values and collect the results in a new list`, function () { 135 | fs1.ap(lst).isEq(lazy.list(10, 20, 30)).should.be.true 136 | fs1.ap(lazy.emptyList).isEq(lazy.emptyList).should.be.true 137 | fs2.ap(lst).isEq(lazy.list(10, 20, 30, 10, 20, 30, 10, 20, 30)).should.be.true 138 | }) 139 | }) 140 | describe(`#flatMap()`, function () { 141 | const lst = lazy.list(1, 2, 3) 142 | const f = x => lazy.list(x * 10) 143 | it(`should map a function that returns a list over a list, collect the results, and flatten the list`, function () { 144 | lst.flatMap(f).isEq(lazy.list(10, 20, 30)).should.be.true 145 | }) 146 | }) 147 | describe(`#then()`, function () { 148 | const lst1 = lazy.list(1, 2, 3) 149 | const lst2 = lazy.list(4, 5, 6) 150 | const f = x => x * 10 151 | const fs = lazy.list(f) 152 | it(`should sequentially apply the elements of one list to another but ignore the values of the first list`, function () { 153 | lst1.then(lst2).isEq(lazy.list(4, 5, 6, 4, 5, 6, 4, 5, 6)).should.be.true 154 | lst1.then(lazy.emptyList).should.equal(lazy.emptyList) 155 | fs.then(lst1).isEq(lazy.list(1, 2, 3, 1, 2, 3, 1, 2, 3)).should.be.true 156 | }) 157 | }) 158 | describe(`#toString()`, function () { 159 | const lst = lazy.list(1, 2, 3) 160 | it(`should return '[Object List]' when cast to a string`, function () { 161 | lst.toString().should.equal(`[Object List]`) 162 | lazy.emptyList.toString().should.equal(`[Object List]`) 163 | }) 164 | }) 165 | describe(`#valueOf()`, function () { 166 | const lst = lazy.list(1, 2, 3) 167 | const str = lazy.list(`a`, `b`, `c`) 168 | it(`should return a list string, e.g. '[1:2:3:[]]', as its value`, function () { 169 | lst.valueOf().should.equal(`[1:2:3:[]]`) 170 | str.valueOf().should.equal(`[abc]`) 171 | lazy.emptyList.valueOf().should.equal(`[]`) 172 | }) 173 | }) 174 | describe(`#typeOf()`, function () { 175 | const lst = lazy.list(1, 2, 3) 176 | const str = lazy.list(`a`, `b`, `c`) 177 | it(`should return its type enclosed in brackets as a string`, function () { 178 | lst.typeOf().should.equal(`[number]`) 179 | str.typeOf().should.equal(`[string]`) 180 | lazy.emptyList.typeOf().should.equal(`[]`) 181 | }) 182 | }) 183 | }) 184 | -------------------------------------------------------------------------------- /test/list-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`list()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | it(`should create a new list from a series of zero or more values`, function () { 6 | lazy.list(1, 2, 3).isEq(lst).should.be.true 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /test/listAppend-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`listAppend()`, function () { 4 | const lst1 = lazy.list(1, 2, 3) 5 | const lst2 = lazy.list(4, 5, 6) 6 | const lst3 = lazy.list(1, 2, 3, 4, 5, 6) 7 | it(`should append one list to another`, function () { 8 | lazy.listAppend(lst1, lst2).isEq(lst3).should.be.true 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/listInf-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`listInf()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | const inf = lazy.listInf(1) 6 | it(`should generate an infinite list`, function () { 7 | lazy.take(3, inf).isEq(lst).should.be.true 8 | lazy.index(inf, 1000).should.equal(1001) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/listInfBy-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`listInfBy()`, function () { 4 | const inf = lazy.listInfBy(1, x => x * 2) 5 | it(`should generate an infinite list`, function () { 6 | lazy.take(3, inf).isEq(lazy.list(1, 2, 4)).should.be.true 7 | lazy.index(inf, 10).should.equal(1024) 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/listRange-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`listRange()`, function () { 4 | const lst1 = lazy.listRange(0, 10) 5 | const lst2 = lazy.listRange(10, 0) 6 | const lst3 = lazy.list(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 7 | const lst4 = lazy.list(10, 9, 8, 7, 6, 5, 4, 3, 2, 1) 8 | it(`should build a list from a range of values`, function () { 9 | lst1.isEq(lst3).should.be.true 10 | lst2.isEq(lst4).should.be.true 11 | }) 12 | it(`should return a singleton list if the start and end values are the same`, function () { 13 | lazy.listRange(1, 1).isEq(lazy.list(1)).should.be.true 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /test/listRangeBy-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`listRangeBy()`, function () { 4 | const lst1 = lazy.listRangeBy(0, 50, x => x + 5) 5 | const lst2 = lazy.listRangeBy(10, 0, x => x - 1) 6 | const lst3 = lazy.list(0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50) 7 | const lst4 = lazy.list(10, 9, 8, 7, 6, 5, 4, 3, 2, 1) 8 | it(`should build a list from a range of values`, function () { 9 | lst1.isEq(lst3).should.be.true 10 | lst2.isEq(lst4).should.be.true 11 | }) 12 | it(`should return a singleton list if the start and end values are the same`, function () { 13 | lazy.listRangeBy(1, 1).isEq(lazy.list(1)).should.be.true 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /test/map-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`map()`, function () { 4 | const lst1 = lazy.list(1, 2, 3) 5 | const lst2 = lazy.list(3, 6, 9) 6 | const f = x => x * 3 7 | it(`should map a function over a list and return the results in a new list`, function () { 8 | lazy.map(f, lst1).isEq(lst2).should.be.true 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/repeat-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`repeat()`, function () { 4 | const inf = lazy.repeat(3) 5 | const lst = lazy.list(3, 3, 3, 3, 3, 3, 3, 3, 3, 3) 6 | it(`should build an infinite list of identical values`, function () { 7 | lazy.take(10, inf).isEq(lst).should.be.true 8 | lazy.index(inf, 100).should.equal(3) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /test/replicate-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`replicate()`, function () { 4 | const lst = lazy.list(3, 3, 3, 3, 3, 3, 3, 3, 3, 3) 5 | it(`should return a list of a specified length in which every value is the same`, function () { 6 | lazy.replicate(10, 3).isEq(lst).should.be.true 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /test/reverse-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`reverse()`, function () { 4 | const lst1 = lazy.list(1, 2, 3) 5 | const lst2 = lazy.list(3, 2, 1) 6 | it(`should reverse a list`, function () { 7 | lazy.reverse(lst1).isEq(lst2).should.be.true 8 | }) 9 | }) 10 | -------------------------------------------------------------------------------- /test/sort-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`sort()`, function () { 4 | const lst1 = lazy.list(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 5 | const lst2 = lazy.list(10, 9, 8, 7, 6, 5, 4, 3, 2, 1) 6 | const lst3 = lazy.list(1, 1, 4, 4, 9, 9, 6, 6, 3, 3, 8, 8, 7, 7, 5, 5, 2, 2, 10, 10) 7 | const lst4 = lazy.list(1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10) 8 | const lst5 = lazy.list(20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9) 9 | const lst6 = lazy.list(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 30) 10 | it(`should sort a list using a merge sort algorithm`, function () { 11 | lazy.sort(lst1).isEq(lst1).should.be.true 12 | lazy.sort(lst2).isEq(lst1).should.be.true 13 | lazy.sort(lazy.list(1)).isEq(lazy.list(1)).should.be.true 14 | lazy.sort(lazy.list()).isEq(lazy.emptyList).should.be.true 15 | lazy.sort(lst3).isEq(lst4).should.be.true 16 | lazy.sort(lst5).isEq(lst6).should.be.true 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/sortBy-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | const LT = lazy.LT 4 | const GT = lazy.GT 5 | const EQ = lazy.EQ 6 | 7 | describe(`sort()`, function () { 8 | const lst1 = lazy.list(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 9 | const lst2 = lazy.list(10, 9, 8, 7, 6, 5, 4, 3, 2, 1) 10 | const lst3 = lazy.listRange(0, 10) 11 | it(`should sort a list using a merge sort algorithm and a custom comparison function`, function () { 12 | lazy.sortBy((a, b) => a === b ? EQ : (a < b ? GT : LT), lst2).isEq(lst2).should.be.true 13 | lazy.sortBy((a, b) => a === b ? EQ : (a < b ? LT : GT), lst3).isEq(lst1).should.be.true 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /test/tail-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`tail()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | it(`should extract the elements after the head of a list`, function () { 6 | lazy.tail(lst).isEq(lazy.list(2, 3)).should.be.true 7 | }) 8 | it(`should throw an error if the list is empty`, function () { 9 | lazy.tail.bind(null, lazy.emptyList).should.throw() 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/take-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`take()`, function () { 4 | const lst = lazy.list(1, 2, 3) 5 | it(`should return the prefix of a list of a given length`, function () { 6 | lazy.take(2, lst).isEq(lazy.list(1, 2)).should.be.true 7 | }) 8 | it(`should return the empty list if the second argument is the empty list`, function () { 9 | lazy.take(2, lazy.list()).should.equal(lazy.emptyList) 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /test/takeWhile-test.js: -------------------------------------------------------------------------------- 1 | import * as lazy from '../source' 2 | 3 | describe(`takeWhile()`, function () { 4 | const lst = lazy.list(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 5 | const f = x => x < 3 6 | it(`should return the longest prefix of a list of values that satisfy a predicate function`, function () { 7 | lazy.takeWhile(f, lst).isEq(lazy.list(0, 1, 2)).should.be.true 8 | }) 9 | it(`should return an empty list if the second argument is an empty list`, function () { 10 | lazy.takeWhile(f, lazy.emptyList).should.equal(lazy.emptyList) 11 | }) 12 | }) 13 | --------------------------------------------------------------------------------