├── .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 | [](https://github.com/sjsyrek/lazy-linked-lists/blob/master/LICENSE.txt)
4 | [](https://travis-ci.org/sjsyrek/lazy-linked-lists)
5 | [](https://codeclimate.com/github/sjsyrek/lazy-linked-lists/coverage)
6 | [](https://www.bithound.io/github/sjsyrek/lazy-linked-lists)
7 | [](https://david-dm.org/sjsyrek/lazy-linked-lists)
8 | [](https://david-dm.org/sjsyrek/lazy-linked-lists?type=dev)
9 | [](https://dependencyci.com/github/sjsyrek/lazy-linked-lists)
10 | [](http://standardjs.com/)
11 | [](https://github.com/semantic-release/semantic-release)
12 | [](https://bestpractices.coreinfrastructure.org/projects/334)
13 |
14 | [](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 |
--------------------------------------------------------------------------------