├── .gitignore ├── .travis.yml ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── assets ├── listopard.png └── listopard.svg ├── codecov.yml ├── package-lock.json ├── package.json ├── src ├── curried.ts ├── devtools.ts ├── fantasy-land.ts ├── index.ts ├── methods.ts └── ramda.ts ├── test ├── bench │ ├── README.md │ ├── append.suite.js │ ├── appendtest.js │ ├── concat.perf.ts │ ├── concat.suite.js │ ├── default-suite.js │ ├── filter.perf.ts │ ├── foldl-iterator.perf.ts │ ├── foldl.perf.ts │ ├── foldl.suite.js │ ├── index.html │ ├── index.js │ ├── index.ts │ ├── insert.perf.ts │ ├── iterator.perf.ts │ ├── iterator.suite.js │ ├── list.ts │ ├── map.perf.ts │ ├── map.suite.js │ ├── package-lock.json │ ├── package.json │ ├── prepare-benchmarks.sh │ ├── prepend.perf.ts │ ├── prepend.suite.js │ ├── random-access.perf.ts │ ├── random-access.suite.js │ ├── report.ts │ ├── slice.perf.ts │ ├── sort.perf.ts │ ├── tsconfig.json │ ├── update.perf.ts │ ├── view.handlebars │ └── webpack.config.js ├── check.ts ├── commands.ts ├── curried.ts ├── fantasy-land.ts ├── index.ts ├── methods.ts ├── property │ └── index.ts ├── ramda.ts ├── tree-shaking │ ├── README.md │ ├── index.html │ ├── package-lock.json │ ├── package.json │ └── src │ │ ├── baseline.js │ │ ├── curried.js │ │ ├── index1.js │ │ ├── index2.js │ │ └── methods.js ├── tsconfig.json └── utils.ts ├── tsconfig-build.json ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | test/bench/hydrogen-* 3 | test/bench/code.asm 4 | test/bench/list-old 5 | test/bench/data.json 6 | test/bench/bundle.js 7 | dist 8 | *~ 9 | .vscode 10 | .nyc_output 11 | tags 12 | coverage 13 | 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | 5 | after_success: 6 | - "npm run codecov" 7 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // This configurtion makes it possible to debug the Mocha tests with 3 | // break points. 4 | "version": "0.2.0", 5 | "configurations": [ 6 | { 7 | "type": "node", 8 | "request": "launch", 9 | "name": "Mocha Tests", 10 | "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", 11 | "protocol": "inspector", 12 | "args": [ 13 | "--no-timeouts", 14 | "-r", 15 | "ts-node/register", 16 | "--colors", 17 | "${workspaceRoot}/test/*.ts" 18 | ], 19 | "outFiles": [ 20 | "${workspaceRoot}/dist" 21 | ], 22 | "sourceMaps": true, 23 | "cwd": "${workspaceRoot}", 24 | "runtimeExecutable": null, 25 | "internalConsoleOptions": "openOnSessionStart", 26 | "stopOnEntry": false, 27 | "env": { 28 | "NODE_ENV": "testing" 29 | }, 30 | "skipFiles": [ 31 | "node_modules/**/*.js", 32 | "/**/*.js" 33 | ] 34 | }, 35 | ] 36 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2018 Simon Friis Vindum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | List logo 3 |

4 | 5 |

6 | A fast immutable list with a functional API. 7 |

8 | 9 |

10 | Gitter 11 | Build Status 12 | codecov 13 |

14 | 15 | # List 16 | 17 | List is a purely functional alternative to arrays. It is an 18 | implementation of a fast persistent sequence data structure. Compared 19 | to JavaScript's `Array` List has three major benefits. 20 | 21 | * **Safety**. List is immutable. This makes it safer and better suited 22 | for functional programming. It doesn't tempt you with an imperative API and 23 | accidental mutations won't be a source of bugs. 24 | * **Performance**. Since List doesn't allow mutations it can be 25 | heavily optimized for pure operations. This makes List much faster for 26 | functional programming than arrays. [See the 27 | benchmarks](https://funkia.github.io/list/benchmarks/). 28 | * **API**: List has a large API of useful functions and offers both chainable 29 | methods and curried functions to suit every taste. 30 | 31 | ## Features 32 | 33 | * **Familiar functional API**. List follows the naming conventions 34 | common in functional programming and has arguments ordered for 35 | currying/partial application. 36 | * **Extensive API**. List has all the functions known from `Array` 37 | and a lot of additional functions that'll save the day once you need them. 38 | * **Extremely fast**. List is a carefully optimized implementation of 39 | the highly efficient data-structure _relaxed radix balanced trees_. We have 40 | an [extensive benchmark suite](https://funkia.github.io/list/benchmarks/) to 41 | ensure optimal performance. Here is an explanation [how](https://monoid.dk/post/how-can-list-be-faster-than-native-arrays/) 42 | * **Several API styles**. In addition to the base API List offers [additional 43 | API styles](#api-styles). Import `list/methods` to get chainable methods or 44 | alterntively import `list/curried` to get a version of the API where every 45 | function is curried. Both variants are 100% TypeScript compatible. 46 | * **Does one thing well**. Instead of offering a wealth of data 47 | structures List has a tight focus on being the best immutable list possible. 48 | It doesn't do everything but is designed to work well with the libraries 49 | you're already using. 50 | * **Seamless Ramda integration**. If you know Ramda you already know 51 | how to use List. List was designed to integrate [seamlessly with 52 | Ramda](#seamless-ramda-integration). 53 | * **Type safe**. List is implemented in TypeScript. It makes full use of 54 | TypeScript features to provide accurate types that covers the entire library. 55 | * **Fully compatible with tree-shaking**. List ships with tree-shaking 56 | compatible ECMAScript modules. `import * as L from "list"` in itself adds 57 | zero bytes to your bundle when using Webpack. Using a function adds only that 58 | function and the very small (<1KB) core of the library. You only pay in size 59 | for the functions that you actually use. 60 | * **Iterable**. Implements the JavaScript iterable protocol. This 61 | means that lists can be use in `for..of` loops, works with destructuring, and 62 | can be passed to any function expecting an iterable. [See more](#iterable). 63 | * **Fantasy Land support**. List 64 | [implements](#fantasy-land--static-land) both the Fantasy Land and the Static 65 | Land specification. 66 | 67 | | Package | Version | Downloads | Dependencies | Dev Deps | Install size | GZIP size | 68 | | ------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | 69 | | `list` | [![npm version](https://badge.fury.io/js/list.svg)](https://www.npmjs.com/package/list) | [![Downloads](https://img.shields.io/npm/dt/list.svg)](https://www.npmjs.com/package/list) | [![Dependency Status](https://david-dm.org/funkia/list/status.svg)](https://david-dm.org/funkia/list) | [![devDependency Status](https://david-dm.org/funkia/list/dev-status.svg)](https://david-dm.org/funkia/list?type=dev) | [![install size](https://packagephobia.now.sh/badge?p=list)](https://packagephobia.now.sh/result?p=list) | [![gzip size](http://img.badgesize.io/https://cdn.jsdelivr.net/npm/list/dist/index.js?compression=gzip)](https://cdn.jsdelivr.net/npm/list/dist/index.js) | 70 | 71 | 72 | ## Getting started 73 | 74 | This section explains how to get started using List. First you'll have 75 | to install the library. 76 | 77 | ``` 78 | npm i list 79 | ``` 80 | 81 | Then you can import it. 82 | 83 | ```js 84 | // As an ES module 85 | import * as L from "list"; 86 | // Or with require 87 | const L = require("list"); 88 | ``` 89 | 90 | Then you can begin using List instead of arrays and enjoy immutability 91 | the performance benefits. 92 | 93 | As a replacement for array literals List offers the function `list` 94 | for constructing lists. Instead of using `[...]` to construct an array 95 | with the content `...` one can use `list(...)` to construct a list 96 | with the same content. Here is an example. 97 | 98 | 99 | ```js 100 | // An array literal 101 | const myArray = [0, 1, 2, 3]; 102 | // The List equivalent 103 | const myList = L.list(0, 1, 2, 3); 104 | ``` 105 | 106 | List has all the common functions that you know from native arrays and 107 | other libraries. 108 | 109 | ```js 110 | const myList = L.list(0, 1, 2, 3, 4, 5); 111 | myList.length; //=> 6 112 | L.filter(isEven, myList); //=> list(0, 2, 4) 113 | L.map(n => n * n, myList); //=> list(0, 1, 4, 9, 16, 25) 114 | L.reduce((sum, n) => sum + n, 0, myList); //=> 15 115 | L.slice(2, 5, myList); //=> list(2, 3, 4) 116 | L.concat(myList, L.list(6, 7, 8)); //=> list(0, 1, 2, 3, 4, 5, 6, 7, 8); 117 | ``` 118 | 119 | You'll probably also end up needing to convert between arrays and 120 | List. You can do that with the functions `from` and `toArray`. 121 | 122 | ```js 123 | L.toArray(L.list("foo", "bar")); //=> ["foo", "bar"]; 124 | L.from(["foo", "bar"]); //=> L.list("foo", "bar"); 125 | ``` 126 | 127 | List offers a wealth of other useful and high-performing functions. 128 | You can see them all in the [API documentation](#api-documentation) 129 | 130 | ## API styles 131 | 132 | List offers several API styles. By default the library exports "plain" 133 | functions. Additionally curried functions can be imported from `list/curried` 134 | and an API with chainable methods can be imported from `list/methods`. The 135 | differences are illustrated below. 136 | 137 | The default export offers normal plain function. 138 | 139 | ```ts 140 | import * as L from "list"; 141 | 142 | const l = L.take(5, L.sortBy(p => p.name, L.filter(p => p.age > 22, people))); 143 | ``` 144 | 145 | In `list/methods` all functions are available as chainable methods. 146 | 147 | ```ts 148 | import * as L from "list/methods"; 149 | 150 | const l = people 151 | .filter(p => p.age > 22) 152 | .sortBy(p => p.name) 153 | .take(5); 154 | ``` 155 | 156 | In `list/curried` all functions are curried. In the example below the partially 157 | applied functions are composed together using Ramda's 158 | [`pipe`](http://ramdajs.com/docs/#pipe). Alternatively one could have used 159 | Lodash's [`flowRight`](https://lodash.com/docs/#flow). 160 | 161 | ```ts 162 | import * as R from "ramda"; 163 | import * as L from "list/curried"; 164 | 165 | const l = R.pipe(L.filter(p => p.age > 22), L.sortBy(p => p.name), L.take(5))( 166 | people 167 | ); 168 | ``` 169 | 170 | ## Iterable 171 | 172 | List implements the JavaScript iterable protocol. This means that 173 | lists can be used with array destructuring just like normal arrays. 174 | 175 | ```js 176 | const myList = L.list("first", "second", "third", "fourth"); 177 | const [first, second] = myList; 178 | first; //=> "first" 179 | second; //=> "second" 180 | ``` 181 | 182 | Lists can also be used in `for..of` loops. 183 | 184 | ```js 185 | for (const element of myList) { 186 | console.log(element); 187 | } 188 | // logs: first, second, third, fourth 189 | ``` 190 | 191 | And they can be passed to any function that takes an iterable as its argument. 192 | As an example a list can be converted into a native 193 | [`Set`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). 194 | 195 | ```js 196 | const mySet = new Set(myList); 197 | mySet.has("third"); //=> true 198 | ``` 199 | 200 | This works because the `Set` constructor accepts any iterable as 201 | argument. 202 | 203 | Lists also work with [spread 204 | syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax). 205 | For instance, you can call a function like this. 206 | 207 | ```js 208 | console.log(...list("hello", "there", "i'm", "logging", "elements")); 209 | ``` 210 | 211 | Then each element of the list will be passed as an argument to `console.log`. 212 | 213 | List also suports iterating backwards over lists through the 214 | [`backwards`](#backwards) function. 215 | 216 | ### Iterator anti-patterns 217 | 218 | The iterable protocol allows for some very convenient patterns and 219 | means that lists can integrate nicely with JavaScript syntax. But, 220 | here are two anti-patterns that you should be aware of. 221 | 222 | 1. Don't overuse `for..of` loops. Functions like [`map`](#map) and 223 | [`foldl`](#foldl) are often a better choice. If you want to perform 224 | a side-effect for each element in a list you should probably use 225 | [`forEach`](#forEach). 226 | 2. Don't use the spread syntax in destructuring 227 | ```js 228 | const [a, b, ...cs] = myList; // Don't do this 229 | ``` 230 | The syntax converts the rest of the iterable (in this case a list) 231 | into an array by iterating through the entire iterable. This is 232 | slow and it turns our list into an array. This alternative avoids 233 | both problems. 234 | ```js 235 | const [[a, b], cs] = splitAt(2, myList); // Do this 236 | ``` 237 | This uses the [`splitAt`](#splitAt) function which splits and 238 | creates the list `cs` very efficiently in `O(log(n))` time. 239 | 240 | ## Seamless Ramda integration 241 | 242 | List is designed to work seamlessly together with Ramda. Ramda offers a large 243 | number of useful functions for working with arrays. List implements the same 244 | functions on its immutable data structure. This means that Ramda users can keep 245 | using the API they're familiar with. Additionally, List offers an entry point 246 | where all functions are curried. 247 | 248 | Since List implements Ramda's array API it is very easy to convert code from 249 | using arrays to using immutable lists. As an example, consider the code below. 250 | 251 | ```js 252 | import * as R from "ramda"; 253 | 254 | R.pipe(R.filter(n => n % 2 === 0), R.map(R.multiply(3)), R.reduce(R.add, 0))( 255 | array 256 | ); 257 | ``` 258 | 259 | The example can be converted to code using List as follows. 260 | 261 | ```js 262 | import * as R from "ramda"; 263 | import * as L from "list/curried"; 264 | 265 | R.pipe(L.filter(n => n % 2 === 0), L.map(R.multiply(3)), L.reduce(R.add, 0))( 266 | list 267 | ); 268 | ``` 269 | 270 | For each function operating on arrays, the `R` is simply changed to an `L`. 271 | This works because List exports functions that have the same names and behavior 272 | as Ramdas functions. 273 | 274 | ### Implemented Ramda functions 275 | 276 | The goal is to implement the entirety of Ramda's array functions for 277 | List. The list below keeps track of how many of Ramda functions that 278 | are missing and of how many that are already implemented. Currently 61 279 | out of 76 functions have been implemented. 280 | 281 | Implemented: `adjust`, `all`, `any`, `append`, `chain`, `concat`, `contains`, 282 | `drop`, `dropLast`, `dropRepeats`, `dropRepeatsWith`, `dropWhile`, `filter`, 283 | `find`, `findIndex`, `findLast`, `group`, `groupWith`, `head`, `flatten`, 284 | `indexOf`, `intersperse`, `init`, `insert`, `insertAll`, `last`, `lastIndexOf`, 285 | `length`, `join`, `map`, `none`, `nth`, `pair`, `partition`, `pluck`, 286 | `prepend`, `range`, `reduce`, `reduceRight`, `reduceWhile`, `reject`, `remove`, 287 | `reverse`, `repeat`, `scan`, `sequence`, `slice`, `sort`, `splitAt`, 288 | `splitEvery`, `splitWhen`, `take`, `takeWhile`, `tail`, 289 | `takeLast`,`takeLastWhile`, `traverse`, `times`, `update`, `zip`, `zipWith`. 290 | 291 | Not implemented: `aperture`, `dropLastWhile`, `endsWith`, `findLastIndex`, 292 | `indexBy`, `mapAccum`, `mapAccumRight`, `startsWith`, `transpose`, `unfold`, 293 | `uniq`, `uniqBy`, `uniqWith`, `unnest` `without`, `xprod`. 294 | 295 | ### Differences compared to Ramda 296 | 297 | While List tries to stay as close to Ramda's API as possible there are a few 298 | deviations to be aware of. 299 | 300 | * List's curried functions do not support the `R.__` placeholder. Instead of 301 | `R.reduce(R.__, 0, l)` one alternative is to use an arrow function `_ => 302 | L.reduce(_, 0, l)` instead. 303 | * [`sort`](#sort) and [`sortWith`](#sortwith) are different compared to what 304 | they do in Ramda. `L.sortWith` is equivalent to `R.sort` and `L.sort` sorts a 305 | list without taking a comparison function. This makes the common case of 306 | sorting a list of numbers or strings easier 307 | 308 | ## Fantasy Land & Static Land 309 | 310 | Fantasy Land 311 | 312 | 313 | List currently implements the following Fantasy Land and Static Land 314 | specifications: Setoid, semigroup, monoid, foldable, functor, apply, 315 | applicative, chain, monad. 316 | 317 | The following specifications have not been implemented yet: 318 | Traversable, Ord. 319 | 320 | Since methods hinder tree-shaking the Fantasy Land methods are not 321 | included by default. In order to get them you must import it likes 322 | this: 323 | 324 | ```js 325 | import "list/fantasy-land"; 326 | ``` 327 | 328 | ## API documentation 329 | 330 | The API is organized into three parts. 331 | 332 | 1. [Creating lists](#creating-lists) — Functions that _create_ lists. 333 | 2. [Updating lists](#updating-lists) — Functions that _transform_ lists. 334 | That is, functions that take one or more lists as arguments and 335 | returns a new list. 336 | 3. [Folds](#folds) — Functions that _extracts_ values based on lists. 337 | They take one or more lists as arguments and returns something that 338 | is not a list. 339 | 340 | ### Creating lists 341 | 342 | ### `list` 343 | 344 | Creates a list based on the arguments given. 345 | 346 | **Complexity**: `O(n)` 347 | 348 | **Example** 349 | 350 | ```js 351 | const l = list(1, 2, 3, 4); // creates a list of four elements 352 | const l2 = list("foo"); // creates a singleton 353 | ``` 354 | 355 | ### `empty` 356 | 357 | Returns an empty list. 358 | 359 | **Complexity**: `O(1)` 360 | 361 | **Example** 362 | 363 | ```js 364 | const emptyList = empty(); //=> list() 365 | ``` 366 | 367 | ### `of` 368 | 369 | Takes a single arguments and returns a singleton list that contains it. 370 | 371 | **Complexity**: `O(1)` 372 | 373 | **Example** 374 | 375 | ```js 376 | of("foo"); //=> list("foo") 377 | ``` 378 | 379 | ### `pair` 380 | 381 | Takes two arguments and returns a list that contains them. 382 | 383 | **Complexity**: `O(1)` 384 | 385 | **Example** 386 | 387 | ```js 388 | pair("foo", "bar"); //=> list("foo", "bar") 389 | ``` 390 | 391 | ### `from` 392 | 393 | Converts an array, an array-like or an itearble into a list. 394 | 395 | **Complexity**: `O(n)` 396 | 397 | **Example** 398 | 399 | ```js 400 | from([0, 1, 2, 3, 4]); //=> list(0, 1, 2, 3, 4) 401 | ``` 402 | 403 | ### `range` 404 | 405 | Returns a list of numbers between an inclusive lower bound and an 406 | exclusive upper bound. 407 | 408 | **Complexity**: `O(n)` 409 | 410 | **Example** 411 | 412 | ```js 413 | range(3, 8); //=> list(3, 4, 5, 6, 7) 414 | ``` 415 | 416 | ### `repeat` 417 | 418 | Returns a list of a given length that contains the specified value in 419 | all positions. 420 | 421 | **Complexity**: `O(n)` 422 | 423 | **Example** 424 | 425 | ```js 426 | repeat(1, 7); //=> list(1, 1, 1, 1, 1, 1, 1) 427 | repeat("foo", 3); //=> list("foo", "foo", "foo") 428 | ``` 429 | 430 | ### `times` 431 | 432 | Returns a list of given length that contains the value of the given function called with current index. 433 | 434 | **Complexity**: `O(n)` 435 | 436 | **Example** 437 | 438 | ```js 439 | const twoFirsOdds = times(i => i * 2 + 1, 2); 440 | const dots = times(() => { 441 | const x = Math.random() * width; 442 | const y = Math.random() * height; 443 | return { x, y }; 444 | }, 50); 445 | ``` 446 | 447 | ### Updating lists 448 | 449 | ### `concat` 450 | 451 | Concatenates two lists. 452 | 453 | **Complexity**: `O(log(n))` 454 | 455 | **Example** 456 | 457 | ```js 458 | concat(list(0, 1, 2), list(3, 4)); //=> list(0, 1, 2, 3, 4) 459 | ``` 460 | 461 | ### `flatten` 462 | 463 | Flattens a list of lists into a list. Note that this function does 464 | _not_ flatten recursively. It removes one level of nesting only. 465 | 466 | **Complexity**: `O(n * log(m))` where `n` is the length of the outer 467 | list and `m` the length of the inner lists. 468 | 469 | **Example** 470 | 471 | ```js 472 | const nested = list(list(0, 1, 2, 3), list(4), empty(), list(5, 6)); 473 | flatten(nested); //=> list(0, 1, 2, 3, 4, 5, 6) 474 | ``` 475 | 476 | ### `prepend` 477 | 478 | Prepends an element to the front of a list and returns the new list. 479 | 480 | **Complexity**: `O(log(n))`, practically constant 481 | 482 | **Example** 483 | 484 | ```js 485 | const newList = prepend(0, list(1, 2, 3)); //=> list(0, 1, 2, 3) 486 | ``` 487 | 488 | ### `append` 489 | 490 | Appends an element to the end of a list and returns the new list. 491 | 492 | **Complexity**: `O(log(n))`, practically constant 493 | 494 | **Example** 495 | 496 | ```js 497 | const newList = append(3, list(0, 1, 2)); //=> list(0, 1, 2, 3) 498 | ``` 499 | 500 | ### `intersperse` 501 | 502 | Inserts a separator between each element in a list. 503 | 504 | **Example** 505 | 506 | ```js 507 | intersperse("n", list("ba", "a", "a")); //=> list("ba", "n", "a", "n", "a") 508 | ``` 509 | 510 | ### `map` 511 | 512 | Applies a function to each element in the given list and returns a new 513 | list of the values that the function return. 514 | 515 | **Complexity**: `O(n)` 516 | 517 | **Example** 518 | 519 | ```js 520 | map(n => n * n, list(0, 1, 2, 3, 4)); //=> list(0, 1, 4, 9, 16) 521 | ``` 522 | 523 | ### `pluck` 524 | 525 | Extracts the specified property from each object in the list. 526 | 527 | **Example** 528 | 529 | ```js 530 | const l = list( 531 | { foo: 0, bar: "a" }, 532 | { foo: 1, bar: "b" }, 533 | { foo: 2, bar: "c" } 534 | ); 535 | pluck("foo", l); //=> list(0, 1, 2) 536 | ``` 537 | 538 | ### `update` 539 | 540 | Returns a list that has the entry specified by the index replaced with 541 | the given value. 542 | 543 | If the index is out of bounds the given list is 544 | returned unchanged. 545 | 546 | **Complexity**: `O(log(n))` 547 | 548 | **Example** 549 | 550 | ```js 551 | update(2, "X", list("a", "b", "c", "d", "e")); //=> list("a", "b", "X", "d", "e") 552 | ``` 553 | 554 | ### `adjust` 555 | 556 | Returns a list that has the entry specified by the index replaced with 557 | the value returned by applying the function to the value. 558 | 559 | If the index is out of bounds the given list is 560 | returned unchanged. 561 | 562 | **Complexity**: `O(log(n))` 563 | 564 | **Example** 565 | 566 | ```js 567 | adjust(2, inc, list(0, 1, 2, 3, 4, 5)); //=> list(0, 1, 3, 3, 4, 5) 568 | ``` 569 | 570 | ### `slice` 571 | 572 | Returns a slice of a list. Elements are removed from the beginning and 573 | end. Both the indices can be negative in which case they will count 574 | from the right end of the list. 575 | 576 | **Complexity**: `O(log(n))` 577 | 578 | **Example** 579 | 580 | ```js 581 | const l = list(0, 1, 2, 3, 4, 5); 582 | slice(1, 4, l); //=> list(1, 2, 3) 583 | slice(2, -2, l); //=> list(2, 3) 584 | ``` 585 | 586 | ### `take` 587 | 588 | Takes the first `n` elements from a list and returns them in a new list. 589 | 590 | **Complexity**: `O(log(n))` 591 | 592 | **Example** 593 | 594 | ```js 595 | take(3, list(0, 1, 2, 3, 4, 5)); //=> list(0, 1, 2) 596 | ``` 597 | 598 | ### `takeWhile` 599 | 600 | Takes the first elements in the list for which the predicate returns 601 | `true`. 602 | 603 | **Complexity**: `O(k + log(n))` where `k` is the number of elements 604 | satisfying the predicate. 605 | 606 | **Example** 607 | 608 | ```js 609 | takeWhile(n => n < 4, list(0, 1, 2, 3, 4, 5, 6)); //=> list(0, 1, 2, 3) 610 | takeWhile(_ => false, list(0, 1, 2, 3, 4, 5)); //=> list() 611 | ``` 612 | 613 | ### `takeLast` 614 | 615 | Takes the last `n` elements from a list and returns them in a new 616 | list. 617 | 618 | **Complexity**: `O(log(n))` 619 | 620 | **Example** 621 | 622 | ```js 623 | takeLast(3, list(0, 1, 2, 3, 4, 5)); //=> list(3, 4, 5) 624 | ``` 625 | 626 | ### `takeLastWhile` 627 | 628 | Takes the last elements in the list for which the predicate returns 629 | `true`. 630 | 631 | **Complexity**: `O(k + log(n))` where `k` is the number of elements 632 | satisfying the predicate. 633 | 634 | **Example** 635 | 636 | ```js 637 | takeLastWhile(n => n > 2, list(0, 1, 2, 3, 4, 5)); //=> list(3, 4, 5) 638 | takeLastWhile(_ => false, list(0, 1, 2, 3, 4, 5)); //=> list() 639 | ``` 640 | 641 | ### `splitAt` 642 | 643 | Splits a list at the given index and return the two sides in a pair. 644 | The left side will contain all elements before but not including the 645 | element at the given index. The right side contains the element at the 646 | index and all elements after it. 647 | 648 | **Complexity**: `O(log(n))` 649 | 650 | **Example** 651 | 652 | ```js 653 | const l = list(0, 1, 2, 3, 4, 5, 6, 7, 8); 654 | splitAt(4, l); //=> [list(0, 1, 2, 3), list(4, 5, 6, 7, 8)] 655 | ``` 656 | 657 | ### `splitWhen` 658 | 659 | Splits a list at the first element in the list for which the given 660 | predicate returns `true`. 661 | 662 | **Complexity**: `O(n)` 663 | 664 | **Example** 665 | 666 | ```js 667 | const l = list(0, 1, 2, 3, 4, 5, 6, 7); 668 | splitWhen((n) => n > 3, l); //=> [list(0, 1, 2, 3), list(4, 5, 6, 7)] 669 | ``` 670 | 671 | ### `remove` 672 | 673 | Takes an index, a number of elements to remove and a list. Returns a 674 | new list with the given amount of elements removed from the specified 675 | index. 676 | 677 | **Complexity**: `O(log(n))` 678 | 679 | **Example** 680 | 681 | ```js 682 | const l = list(0, 1, 2, 3, 4, 5, 6, 7, 8); 683 | remove(4, 3, l); //=> list(0, 1, 2, 3, 7, 8) 684 | remove(2, 5, l); //=> list(0, 1, 7, 8) 685 | ``` 686 | 687 | ### `drop` 688 | 689 | Returns a new list without the first `n` elements. 690 | 691 | **Complexity**: `O(log(n))` 692 | 693 | **Example** 694 | 695 | ```js 696 | drop(2, list(0, 1, 2, 3, 4, 5)); //=> list(2, 3, 4, 5) 697 | ``` 698 | 699 | ### `dropWhile` 700 | 701 | Removes the first elements in the list for which the predicate returns 702 | `true`. 703 | 704 | **Complexity**: `O(k + log(n))` where `k` is the number of elements 705 | satisfying the predicate. 706 | 707 | **Example** 708 | 709 | ```js 710 | dropWhile(n => n < 4, list(0, 1, 2, 3, 4, 5, 6)); //=> list(4, 5, 6) 711 | ``` 712 | 713 | ### `dropLast` 714 | 715 | Returns a new list without the last `n` elements. 716 | 717 | **Complexity**: `O(log(n))` 718 | 719 | **Example** 720 | 721 | ```js 722 | dropLast(2, list(0, 1, 2, 3, 4, 5)); //=> list(0, 1, 2, 3) 723 | ``` 724 | 725 | ### `dropRepeats` 726 | 727 | Returns a new list without repeated elements. 728 | 729 | **Complexity**: `O(n)` 730 | 731 | **Example** 732 | 733 | ```js 734 | dropRepeats(L.list(0, 0, 1, 1, 1, 2, 3, 3, 4, 4)); //=> list(0, 1, 2, 3, 4) 735 | ``` 736 | 737 | ### `dropRepeatsWith` 738 | 739 | Returns a new list without repeated elements by using the given 740 | function to determine when elements are equal. 741 | 742 | **Complexity**: `O(n)` 743 | 744 | **Example** 745 | 746 | ```js 747 | dropRepeatsWith( 748 | (n, m) => Math.floor(n) === Math.floor(m), 749 | list(0, 0.4, 1.2, 1.1, 1.8, 2.2, 3.8, 3.4, 4.7, 4.2) 750 | ); //=> list(0, 1, 2, 3, 4) 751 | ``` 752 | 753 | ### `tail` 754 | 755 | Returns a new list with the first element removed. If the list is 756 | empty the empty list is returne. 757 | 758 | **Complexity**: `O(1)` 759 | 760 | **Example** 761 | 762 | ```js 763 | tail(list(0, 1, 2, 3)); //=> list(1, 2, 3) 764 | tail(empty()); //=> list() 765 | ``` 766 | 767 | ### `pop` 768 | 769 | Returns a new list with the last element removed. If the list is empty 770 | the empty list is returned. 771 | 772 | **Aliases**: `init` 773 | 774 | **Complexity**: `O(1)` 775 | 776 | **Example** 777 | 778 | ```js 779 | pop(list(0, 1, 2, 3)); //=> list(0, 1, 2) 780 | ``` 781 | 782 | ### `filter` 783 | 784 | Returns a new list that only contains the elements of the original 785 | list for which the predicate returns `true`. 786 | 787 | **Complexity**: `O(n)` 788 | 789 | **Example** 790 | 791 | ```js 792 | filter(isEven, list(0, 1, 2, 3, 4, 5, 6)); //=> list(0, 2, 4, 6) 793 | ``` 794 | 795 | ### `reject` 796 | 797 | Returns a new list that only contains the elements of the original 798 | list for which the predicate returns `false`. 799 | 800 | **Complexity**: `O(n)` 801 | 802 | **Example** 803 | 804 | ```js 805 | reject(isEven, list(0, 1, 2, 3, 4, 5, 6)); //=> list(1, 3, 5) 806 | ``` 807 | 808 | ### `reverse` 809 | 810 | Reverses a list. 811 | 812 | **Complexity**: `O(n)` 813 | 814 | **Example** 815 | 816 | ```js 817 | reverse(list(0, 1, 2, 3, 4, 5)); //=> list(5, 4, 3, 2, 1, 0) 818 | ``` 819 | 820 | ### `ap` 821 | 822 | Applies a list of functions to a list of values. 823 | 824 | **Example** 825 | 826 | ```js 827 | ap(list((n: number) => n + 2, n => 2 * n, n => n * n), list(1, 2, 3)); //=> list(3, 4, 5, 2, 4, 6, 1, 4, 9) 828 | ``` 829 | 830 | ### `chain` 831 | 832 | Maps a function over a list and concatenates all the resulting lists 833 | together. 834 | 835 | **Aliases**: `flatMap` 836 | 837 | **Example** 838 | 839 | ```js 840 | chain(n => list(n, 2 * n, n * n), list(1, 2, 3)); //=> list(1, 2, 1, 2, 4, 4, 3, 6, 9) 841 | ``` 842 | 843 | ### `partition` 844 | 845 | Splits the list into two lists. One list that contains all the values 846 | for which the predicate returns `true` and one containing the values for 847 | which it returns `false`. 848 | 849 | **Complexity**: `O(n)` 850 | 851 | **Example** 852 | 853 | ```js 854 | partition(isEven, list(0, 1, 2, 3, 4, 5)); //=> list(list(0, 2, 4), list(1, 3, 5)) 855 | ``` 856 | 857 | ### `insert` 858 | 859 | Inserts the given element at the given index in the list. 860 | 861 | **Complexity**: `O(log(n))` 862 | 863 | **Example** 864 | 865 | ```js 866 | insert(2, "c", list("a", "b", "d", "e")); //=> list("a", "b", "c", "d", "e") 867 | ``` 868 | 869 | ### `insertAll` 870 | 871 | Inserts the given list of elements at the given index in the list. 872 | 873 | **Complexity**: `O(log(n))` 874 | 875 | **Example** 876 | 877 | ```js 878 | insertAll(2, list("c", "d"), list("a", "b", "e", "f")); //=> list("a", "b", "c", "d", "e", "f") 879 | ``` 880 | 881 | ### `zipWith` 882 | 883 | This is like mapping over two lists at the same time. The two lists are 884 | iterated over in parallel and each pair of elements is passed to the function. 885 | The returned values are assembled into a new list. 886 | 887 | The shortest list determine the size of the result. 888 | 889 | **Complexity**: `O(log(n))` where `n` is the length of the smallest list. 890 | 891 | **Example** 892 | 893 | ```js 894 | const names = list("Turing", "Curry"); 895 | const years = list(1912, 1900); 896 | zipWith((name, year) => ({ name, year }), names, years); 897 | //=> list({ name: "Turing", year: 1912 }, { name: "Curry", year: 1900 }); 898 | ``` 899 | 900 | ### `zip` 901 | 902 | Iterate over two lists in parallel and collect the pairs. 903 | 904 | **Complexity**: `O(log(n))` where `n` is the length of the smallest list. 905 | 906 | **Example** 907 | 908 | ```js 909 | const names = list("a", "b", "c", "d", "e"); 910 | const years = list(0, 1, 2, 3, 4, 5, 6); 911 | //=> list(["a", 0], ["b", 1], ["c", 2], ["d", 3], ["e", 4]); 912 | ``` 913 | 914 | ### `sort` 915 | 916 | Sorts the given list. The list should contain values that can be compared using 917 | the `<` operator or values that implement the Fantasy Land 918 | [Ord](https://github.com/fantasyland/fantasy-land#ord) specification. 919 | 920 | Performs a stable sort. 921 | 922 | **Complexity**: `O(n * log(n))` 923 | 924 | **Example** 925 | 926 | ```js 927 | sort(list(5, 3, 1, 8, 2)); //=> list(1, 2, 3, 5, 8) 928 | sort(list("e", "a", "c", "b", "d"); //=> list("a", "b", "c", "d", "e") 929 | ``` 930 | 931 | ### `sortBy` 932 | 933 | Sort the given list by passing each value through the function and comparing 934 | the resulting value. The function should either return values comparable using 935 | `<` or values that implement the Fantasy Land 936 | [Ord](https://github.com/fantasyland/fantasy-land#ord) specification. 937 | 938 | Performs a stable sort. 939 | 940 | **Complexity**: `O(n * log(n))` 941 | 942 | **Example** 943 | 944 | ```js 945 | sortBy( 946 | o => o.n, 947 | list({ n: 4, m: "foo" }, { n: 3, m: "bar" }, { n: 1, m: "baz" }) 948 | ); 949 | //=> list({ n: 1, m: "baz" }, { n: 3, m: "bar" }, { n: 4, m: "foo" }) 950 | 951 | sortBy(s => s.length, list("foo", "bar", "ba", "aa", "list", "z")); 952 | //=> list("z", "ba", "aa", "foo", "bar", "list") 953 | ``` 954 | 955 | ### `sortWith` 956 | 957 | Sort the given list by comparing values using the given function. The function 958 | receieves two values and should return `-1` if the first value is stricty 959 | larger than the second, `0` is they are equal and `1` if the first values is 960 | strictly smaller than the second. 961 | 962 | Note that the comparison function is equivalent to the one required by 963 | [`Array.prototype.sort`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). 964 | 965 | Performs a stable sort. 966 | 967 | **Complexity**: `O(n * log(n))` 968 | 969 | **Example** 970 | 971 | ```js 972 | sortWith((a, b) => { 973 | if (a === b) { 974 | return 0; 975 | } else if (a < b) { 976 | return -1; 977 | } else { 978 | return 1; 979 | } 980 | }, list(5, 3, 1, 8, 2)); //=> list(1, 2, 3, 5, 8) 981 | ``` 982 | 983 | ### `group` 984 | 985 | Returns a list of lists where each sublist's elements 986 | are all equal. 987 | 988 | **Complexity**: `O(n)` 989 | 990 | **Example** 991 | 992 | ```js 993 | group(list(0, 0, 1, 2, 2, 2, 3, 3)); //=> list(list(0, 0), list(1), list(2, 2, 2), list(3, 3)) 994 | ``` 995 | 996 | ### `groupWith` 997 | 998 | Returns a list of lists where each sublist's elements are pairwise 999 | equal based on the given comparison function. 1000 | 1001 | Note that only adjacent elements are compared for equality. If all 1002 | equal elements should be grouped together the list should be sorted 1003 | before grouping. 1004 | 1005 | **Complexity**: `O(n)` 1006 | 1007 | **Example** 1008 | 1009 | ```js 1010 | const floorEqual = (a, b) => Math.round(a) === Math.round(b); 1011 | groupWith(floorEqual, list(1.1, 1.3, 1.8, 2, 2.2, 3.3, 3.4)); 1012 | //=> list(list(1.1, 1.3), list(1.8, 2, 2.2), list(3.3, 3.4)) 1013 | 1014 | const sameLength = (a, b) => a.length === b.length; 1015 | groupWith(sameLength, list("foo", "bar", "ab", "bc", "baz")); 1016 | //=> list(list("foo", "bar"), list("ab", "bc"), list("baz)) 1017 | ``` 1018 | 1019 | ### Folds 1020 | 1021 | ### `isList` 1022 | 1023 | Returns `true` if the given argument is a list. 1024 | 1025 | **Complexity**: `O(1)` 1026 | 1027 | **Example** 1028 | 1029 | ```js 1030 | isList([0, 1, 2]); //=> false 1031 | isList("string"); //=> false 1032 | isList({ foo: 0, bar: 1 }); //=> false 1033 | isList(list(0, 1, 2)); //=> true 1034 | ``` 1035 | 1036 | ### `isEmpty` 1037 | 1038 | Returns `true` if the list is empty. 1039 | 1040 | **Complexity**: `O(1)` 1041 | 1042 | **Example** 1043 | 1044 | ```js 1045 | isEmpty(empty()); //=> true 1046 | isEmpty(list()); //=> true 1047 | isEmpty(list(0, 1, 2)); //=> false 1048 | ``` 1049 | 1050 | ### `equals` 1051 | 1052 | Returns true if the two lists are equivalent. 1053 | 1054 | **Complexity**: `O(n)` 1055 | 1056 | **Example** 1057 | 1058 | ```js 1059 | equals(list(0, 1, 2, 3), list(0, 1, 2, 3)); //=> true 1060 | equals(list("a", "b", "c"), list("a", "z", "c")); //=> false 1061 | ``` 1062 | 1063 | ### `equalsWith` 1064 | 1065 | Returns `true` if the two lists are equivalent when comparing each 1066 | pair of elements with the given comparison function. 1067 | 1068 | **Complexity**: `O(n)` 1069 | 1070 | **Example** 1071 | 1072 | ```js 1073 | equalsWith( 1074 | (n, m) => n.length === m.length, 1075 | list("foo", "hello", "one"), 1076 | list("bar", "world", "two") 1077 | ); //=> true 1078 | ``` 1079 | 1080 | ### `toArray` 1081 | 1082 | Converts a list into an array. 1083 | 1084 | **Complexity**: `O(n)` 1085 | 1086 | **Example** 1087 | 1088 | ```js 1089 | toArray(list(0, 1, 2, 3, 4)); //=> [0, 1, 2, 3, 4] 1090 | ``` 1091 | 1092 | ### `backwards` 1093 | 1094 | Returns an iterable that iterates backwards over the given list. 1095 | 1096 | **Complexity**: `O(1)` 1097 | 1098 | **Example** 1099 | 1100 | ```js 1101 | const l = list(0, 1, 2, 3, 4) 1102 | for (const n of backwards(l)) { 1103 | if (l < 2) { 1104 | break; 1105 | } 1106 | console.log(l); 1107 | } 1108 | // => logs 4, 3, and then 2 1109 | ``` 1110 | 1111 | ### `nth` 1112 | 1113 | Gets the `n`th element of the list. If `n` is out of bounds 1114 | `undefined` is returned. 1115 | 1116 | **Complexity**: `O(log(n))`, practically constant 1117 | 1118 | **Example** 1119 | 1120 | ```js 1121 | const l = list(0, 1, 2, 3, 4); 1122 | nth(2, l); //=> 2 1123 | ``` 1124 | 1125 | ### `length` 1126 | 1127 | Returns the length of a list. I.e. the number of elements that it 1128 | contains. 1129 | 1130 | **Complexity**: `O(1)` 1131 | 1132 | **Example** 1133 | 1134 | ```js 1135 | length(list(0, 1, 2, 3)); //=> 4 1136 | ``` 1137 | 1138 | ### `first` 1139 | 1140 | Returns the first element of the list. If the list is empty the 1141 | function returns `undefined`. 1142 | 1143 | **Aliases**: `head` 1144 | 1145 | **Complexity**: `O(1)` 1146 | 1147 | **Example** 1148 | 1149 | ```js 1150 | first(list(0, 1, 2, 3)); //=> 0 1151 | first(list()); //=> undefined 1152 | ``` 1153 | 1154 | ### `last` 1155 | 1156 | Returns the last element of the list. If the list is empty the 1157 | function returns `undefined`. 1158 | 1159 | **Complexity**: `O(1)` 1160 | 1161 | **Example** 1162 | 1163 | ```js 1164 | last(list(0, 1, 2, 3)); //=> 3 1165 | last(list()); //=> undefined 1166 | ``` 1167 | 1168 | ### `foldl` 1169 | 1170 | Folds a function over a list. Left-associative. 1171 | 1172 | **Aliases**: `reduce` 1173 | 1174 | **Complexity**: `O(n)` 1175 | 1176 | **Example** 1177 | 1178 | ```js 1179 | foldl((n, m) => n - m, 1, list(2, 3, 4, 5)); 1180 | 1 - 2 - 3 - 4 - 5; //=> -13 1181 | ``` 1182 | 1183 | ### `foldr` 1184 | 1185 | Folds a function over a list. Right-associative. 1186 | 1187 | **Aliases**: `reduceRight` 1188 | 1189 | **Complexity**: `O(n)` 1190 | 1191 | **Example** 1192 | 1193 | ```js 1194 | foldr((n, m) => n - m, 5, list(1, 2, 3, 4)); 1195 | 1 - (2 - (3 - (4 - 5))); //=> 3 1196 | ``` 1197 | 1198 | ### `foldlWhile` 1199 | 1200 | Similar to `foldl`. But, for each element it calls the predicate function 1201 | _before_ the folding function and stops folding if it returns `false`. 1202 | 1203 | **Aliases**: `reduceWhile` 1204 | 1205 | **Complexity**: `O(n)` 1206 | 1207 | **Example** 1208 | 1209 | ```js 1210 | const isOdd = (_acc:, x) => x % 2 === 1; 1211 | 1212 | const xs = L.list(1, 3, 5, 60, 777, 800); 1213 | foldlWhile(isOdd, (n, m) => n + m, 0, xs) //=> 9 1214 | 1215 | const ys = L.list(2, 4, 6); 1216 | foldlWhile(isOdd, (n, m) => n + m, 111, ys) //=> 111 1217 | ``` 1218 | 1219 | ### `scan` 1220 | 1221 | Folds a function over a list from left to right while collecting all the 1222 | intermediate steps in a resulting list. 1223 | 1224 | **Complexity**: `O(n)` 1225 | 1226 | **Example** 1227 | 1228 | ```js 1229 | const l = list(1, 3, 5, 4, 2); 1230 | L.scan((n, m) => n + m, 0, l); //=> list(0, 1, 4, 9, 13, 15)); 1231 | L.scan((s, m) => s + m.toString(), "", l); //=> list("", "1", "13", "135", "1354", "13542") 1232 | ``` 1233 | 1234 | ### `traverse` 1235 | 1236 | Map each element of list to an applicative, evaluate these 1237 | applicatives from left to right, and collect the results. 1238 | 1239 | This works with Fantasy Land 1240 | [applicatives](https://github.com/fantasyland/fantasy-land#applicative). 1241 | 1242 | **Complexity**: `O(n)` 1243 | 1244 | **Example** 1245 | 1246 | ```js 1247 | const safeDiv = n => d => d === 0 ? nothing : just(n / d) 1248 | 1249 | L.traverse(Maybe, safeDiv(10), list(2, 4, 5)); //=> just(list(5, 2.5, 2)) 1250 | L.traverse(Maybe, safeDiv(10), list(2, 0, 5)); //=> nothing 1251 | ``` 1252 | 1253 | ### `sequence` 1254 | 1255 | Evaluate each applicative in the list from left to right, and 1256 | collect the results. 1257 | 1258 | **Complexity**: `O(n)` 1259 | 1260 | **Example** 1261 | 1262 | ```js 1263 | L.sequence(Maybe, list(just(1), just(2), just(3))); //=> just(list(1, 2, 3)) 1264 | L.sequence(Maybe, list(just(1), just(2), nothing())); //=> nothing 1265 | ``` 1266 | 1267 | ### `forEach` 1268 | 1269 | Invokes a given callback for each element in the list from left to 1270 | right. Returns `undefined`. 1271 | 1272 | This function is very similar to `map`. It should be used instead of 1273 | `map` when the mapping function has side-effects. Whereas `map` 1274 | constructs a new list `forEach` merely returns `undefined`. This makes 1275 | `forEach` faster when the new list is unneeded. 1276 | 1277 | **Complexity**: `O(n)` 1278 | 1279 | **Example** 1280 | 1281 | ```js 1282 | const l = list(0, 1, 2); 1283 | forEach(element => console.log(element)); 1284 | //=> 0 1285 | //=> 1 1286 | //=> 2 1287 | ``` 1288 | 1289 | ### `every` 1290 | 1291 | Returns `true` if and only if the predicate function returns `true` 1292 | for all elements in the given list. 1293 | 1294 | **Aliases**: `all` 1295 | 1296 | **Complexity**: `O(n)` 1297 | 1298 | **Example** 1299 | 1300 | ```js 1301 | const isEven = n => n % 2 === 0; 1302 | every(isEven, empty()); //=> true 1303 | every(isEven, list(2, 4, 6, 8)); //=> true 1304 | every(isEven, list(2, 3, 4, 6, 7, 8)); //=> false 1305 | every(isEven, list(1, 3, 5, 7)); //=> false 1306 | ``` 1307 | 1308 | ### `some` 1309 | 1310 | Returns `true` if and only if there exists an element in the list for 1311 | which the predicate returns `true`. 1312 | 1313 | **Aliases**: `any` 1314 | 1315 | **Complexity**: `O(n)` 1316 | 1317 | **Example** 1318 | 1319 | ```js 1320 | const isEven = n => n % 2 === 0; 1321 | some(isEven, empty()); //=> false 1322 | some(isEven, list(2, 4, 6, 8)); //=> true 1323 | some(isEven, list(2, 3, 4, 6, 7, 8)); //=> true 1324 | some(isEven, list(1, 3, 5, 7)); //=> false 1325 | ``` 1326 | 1327 | ### `indexOf` 1328 | 1329 | Returns the index of the _first_ element in the list that is equal to 1330 | the given element. If no such element is found `-1` is returned. 1331 | 1332 | **Complexity**: `O(n)` 1333 | 1334 | **Example** 1335 | 1336 | ```js 1337 | const l = list(12, 4, 2, 89, 6, 18, 7); 1338 | indexOf(12, l); //=> 0 1339 | indexOf(89, l); //=> 3 1340 | indexOf(10, l); //=> -1 1341 | ``` 1342 | 1343 | ### `lastIndexOf` 1344 | 1345 | Returns the index of the _last_ element in the list that is equal to 1346 | the given element. If no such element is found `-1` is returned. 1347 | 1348 | **Complexity**: `O(n)` 1349 | 1350 | **Example** 1351 | 1352 | ```js 1353 | const l = L.list(12, 4, 2, 18, 89, 2, 18, 7); 1354 | L.lastIndexOf(18, l); //=> 6 1355 | L.lastIndexOf(2, l); //=> 5 1356 | L.lastIndexOf(12, l); //=> 0 1357 | ``` 1358 | 1359 | ### `find` 1360 | 1361 | Returns the _first_ element for which the predicate returns `true`. If 1362 | no such element is found the function returns `undefined`. 1363 | 1364 | **Complexity**: `O(n)` 1365 | 1366 | **Example** 1367 | 1368 | ```js 1369 | find(isEven, list(1, 3, 5, 6, 7, 9, 10)); //=> 6 1370 | find(isEven, list(1, 3, 5, 7, 9)); //=> undefined 1371 | ``` 1372 | 1373 | ### `findLast` 1374 | 1375 | Returns the _last_ element for which the predicate returns `true`. If 1376 | no such element is found the function returns `undefined`. 1377 | 1378 | **Complexity**: `O(n)` 1379 | 1380 | **Example** 1381 | 1382 | ```js 1383 | findLast(isEven, list(1, 3, 5, 6, 7, 8, 9)); //=> 8 1384 | findLast(isEven, list(1, 3, 5, 7, 9)); //=> undefined 1385 | ``` 1386 | 1387 | ### `findIndex` 1388 | 1389 | Returns the index of the first element for which the predicate returns 1390 | `true`. If no such element is found the function returns `-1`. 1391 | 1392 | **Complexity**: `O(n)` 1393 | 1394 | **Example** 1395 | 1396 | ```js 1397 | findIndex(isEven, list(1, 3, 5, 6, 7, 9, 10)); //=> 3 1398 | findIndex(isEven, list(1, 3, 5, 7, 9)); //=> -1 1399 | ``` 1400 | 1401 | ### `none` 1402 | 1403 | Returns `true` if and only if the predicate function returns `false` 1404 | for all elements in the given list. 1405 | 1406 | **Complexity**: `O(n)` 1407 | 1408 | **Example** 1409 | 1410 | ```js 1411 | const isEven = n => n % 2 === 0; 1412 | none(isEven, empty()); //=> true 1413 | none(isEven, list(2, 4, 6, 8)); //=> false 1414 | none(isEven, list(2, 3, 4, 6, 7, 8)); //=> false 1415 | none(isEven, list(1, 3, 5, 7)); //=> true 1416 | ``` 1417 | 1418 | ### `includes` 1419 | 1420 | Returns `true` if the list contains the specified element. Otherwise 1421 | it returns `false`. 1422 | 1423 | **Aliases**: `contains` 1424 | 1425 | **Complexity**: `O(n)` 1426 | 1427 | **Example** 1428 | 1429 | ```js 1430 | includes(3, list(0, 1, 2, 3, 4, 5)); //=> true 1431 | includes(3, list(0, 1, 2, 4, 5)); //=> false 1432 | ``` 1433 | 1434 | ### `join` 1435 | 1436 | Concats the strings in a list separated by a specified separator. 1437 | 1438 | **Complexity**: `O(n)` 1439 | 1440 | **Example** 1441 | 1442 | ```js 1443 | join(", ", list("one", "two", "three")); //=> "one, two, three" 1444 | ``` 1445 | 1446 | ## Benchmarks 1447 | 1448 | The benchmarks are located in the [`bench` directory](/test/bench). 1449 | -------------------------------------------------------------------------------- /assets/listopard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/funkia/list/abbb84698cca132c65a25ee157be797f9d40b836/assets/listopard.png -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: "files" 3 | require_changes: true # only post the comment if coverage changes -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "list", 3 | "version": "2.0.19", 4 | "description": "Fast purely functional immutable lists.", 5 | "author": "Simon Friis Vindum", 6 | "license": "MIT", 7 | "repository": "github:funkia/list", 8 | "keywords": [ 9 | "immutable", 10 | "list", 11 | "functional", 12 | "persistent", 13 | "sequence", 14 | "vector" 15 | ], 16 | "bugs": { 17 | "url": "https://github.com/funkia/list/issues" 18 | }, 19 | "main": "dist/index.js", 20 | "types": "./dist/index.d.ts", 21 | "module": "dist/es/index.js", 22 | "scripts": { 23 | "build": "npm run build-es6; npm run build-cmjs", 24 | "build-es6": "tsc -P ./tsconfig-build.json --outDir 'dist/es' --module es2015", 25 | "build-cmjs": "tsc -P ./tsconfig-build.json", 26 | "postbuild": "babel dist --out-dir dist --no-babelrc --plugins annotate-pure-calls", 27 | "test": "nyc mocha --timeout 10000 --recursive test/*.ts", 28 | "test-watch": "mocha -R progress --timeout 10000 --watch --require ts-node/register --watch-extensions ts test/*.ts", 29 | "test-property": "mocha --timeout 0 --require ts-node/register --recursive test/property/*.ts", 30 | "codecov": "codecov -f coverage/coverage-final.json", 31 | "format": "prettier --write \"{src,test,scripts}/**/*.{js,ts}\"", 32 | "cherry-pick": "cherry-pick --cjs-dir dist --esm-dir dist/es --types-dir dist", 33 | "prepublishOnly": "npm run build; npm run cherry-pick", 34 | "postpublish": "cherry-pick clean", 35 | "release": "np" 36 | }, 37 | "sideEffects": [ 38 | "**/fantasy-land.js", 39 | "**/methods.js" 40 | ], 41 | "devDependencies": { 42 | "@types/chai": "^4.2.3", 43 | "@types/mocha": "^5.2.7", 44 | "@types/ramda": "^0.26.28", 45 | "babel-cli": "^6.26.0", 46 | "babel-core": "^6.26.3", 47 | "babel-plugin-annotate-pure-calls": "^0.4.0", 48 | "chai": "4.2.0", 49 | "cherry-pick": "^0.5.0", 50 | "codecov": "^3.6.1", 51 | "fast-check": "^1.17.0", 52 | "mocha": "^6.2.1", 53 | "np": "^5.1.0", 54 | "nyc": "^14.1.1", 55 | "prettier": "1.18.2", 56 | "proptest": "0.0.4", 57 | "ramda": "0.26.1", 58 | "source-map-support": "^0.5.13", 59 | "ts-node": "^8.4.1", 60 | "tslint": "^5.20.0", 61 | "typescript": "^3.6.3" 62 | }, 63 | "files": [ 64 | "dist", 65 | "methods", 66 | "curried", 67 | "fantasy-land", 68 | "ramda" 69 | ], 70 | "nyc": { 71 | "extension": [ 72 | ".ts" 73 | ], 74 | "require": [ 75 | "ts-node/register", 76 | "source-map-support/register" 77 | ], 78 | "include": [ 79 | "src/**/*.ts" 80 | ], 81 | "exclude": [ 82 | "src/methods.ts" 83 | ], 84 | "reporter": [ 85 | "json", 86 | "html", 87 | "text", 88 | "lcov" 89 | ] 90 | }, 91 | "prettier": {} 92 | } 93 | -------------------------------------------------------------------------------- /src/curried.ts: -------------------------------------------------------------------------------- 1 | import * as L from "./index"; 2 | import { List } from "./index"; 3 | 4 | // All functions of arity 1 are simply re-exported as they don't require currying 5 | export { 6 | Node, 7 | List, 8 | list, 9 | isList, 10 | length, 11 | of, 12 | empty, 13 | first, 14 | head, 15 | last, 16 | flatten, 17 | pop, 18 | init, 19 | tail, 20 | from, 21 | toArray, 22 | reverse, 23 | backwards, 24 | sort, 25 | group, 26 | dropRepeats, 27 | isEmpty 28 | } from "./index"; 29 | 30 | export interface Curried2 { 31 | (a: A): (b: B) => R; 32 | (a: A, b: B): R; 33 | } 34 | 35 | export interface Curried3 { 36 | (a: A, b: B, c: C): R; 37 | (a: A, b: B): (c: C) => R; 38 | (a: A): Curried2; 39 | } 40 | 41 | function curry2(f: Function): any { 42 | return function curried(a: any, b: any): any { 43 | return arguments.length === 2 ? f(a, b) : (b: any) => f(a, b); 44 | }; 45 | } 46 | 47 | function curry3(f: (a: any, b: any, c: any) => any): any { 48 | return function curried(a: any, b: any, c: any): any { 49 | switch (arguments.length) { 50 | case 3: 51 | return f(a, b, c); 52 | case 2: 53 | return (c: any) => f(a, b, c); 54 | default: 55 | // Assume 1 56 | return curry2((b: any, c: any) => f(a, b, c)); 57 | } 58 | }; 59 | } 60 | 61 | function curry4(f: (a: any, b: any, c: any, d: any) => any): any { 62 | return function curried(a: any, b: any, c: any, d: any): any { 63 | switch (arguments.length) { 64 | case 4: 65 | return f(a, b, c, d); 66 | case 3: 67 | return (d: any) => f(a, b, c, d); 68 | case 2: 69 | return curry2((c: any, d: any) => f(a, b, c, d)); 70 | default: 71 | // Assume 1 72 | return curry3((b, c, d) => f(a, b, c, d)); 73 | } 74 | }; 75 | } 76 | 77 | // Arity 2 78 | 79 | export const prepend: typeof L.prepend & 80 | ((value: A) => (l: List) => List) = curry2(L.prepend); 81 | 82 | export const append: typeof prepend = curry2(L.append); 83 | 84 | export const pair: typeof L.pair & 85 | ((first: A) => (second: A) => List) = curry2(L.pair); 86 | 87 | export const repeat: typeof L.repeat & 88 | ((value: A) => (times: number) => List) = curry2(L.repeat); 89 | 90 | export const times: typeof L.times & 91 | ((func: (index: number) => A) => (times: number) => List) = curry2( 92 | L.times 93 | ); 94 | 95 | export const nth: typeof L.nth & 96 | ((index: number) => (l: List) => A | undefined) = curry2(L.nth); 97 | 98 | export const map: typeof L.map & 99 | ((f: (a: A) => B) => (l: List) => List) = curry2(L.map); 100 | 101 | export const forEach: typeof L.forEach & 102 | ((callback: (a: A) => void) => (l: List) => void) = curry2(L.forEach); 103 | 104 | export const pluck: typeof L.pluck & 105 | (( 106 | key: K 107 | ) => (l: List) => List) = curry2( 108 | L.pluck 109 | ); 110 | 111 | export const intersperse: typeof prepend = curry2(L.intersperse); 112 | 113 | export const range: typeof L.range & 114 | ((start: number) => (end: number) => List) = curry2(L.range); 115 | 116 | export const filter: typeof L.filter & 117 | ((predicate: (a: A) => a is B) => (l: List) => List) & 118 | ((predicate: (a: A) => boolean) => (l: List) => List) = curry2( 119 | L.filter 120 | ); 121 | 122 | export const reject: typeof filter = curry2(L.reject); 123 | 124 | export const partition: typeof L.partition & 125 | (( 126 | predicate: (a: A) => boolean 127 | ) => (l: List) => [List, List]) = curry2(L.partition); 128 | 129 | export const join: typeof L.join & 130 | ((seperator: string) => (l: List) => List) = curry2(L.join); 131 | 132 | export const ap: typeof L.ap & 133 | ((listF: List<(a: A) => B>) => (l: List) => List) = curry2(L.ap); 134 | 135 | export const flatMap: typeof L.flatMap & 136 | ((f: (a: A) => List) => (l: List) => List) = curry2(L.flatMap); 137 | 138 | export const chain = flatMap; 139 | 140 | export const every: typeof L.every & 141 | ((predicate: (a: A) => boolean) => (l: List) => boolean) = curry2( 142 | L.every 143 | ); 144 | 145 | export const all: typeof every = curry2(L.all); 146 | 147 | export const some: typeof every = curry2(L.some); 148 | 149 | export const any: typeof every = curry2(L.any); 150 | 151 | export const none: typeof every = curry2(L.none); 152 | 153 | export const find: typeof L.find & 154 | ((predicate: (a: A) => boolean) => (l: List) => A | undefined) = curry2( 155 | L.find 156 | ); 157 | 158 | export const findLast: typeof find = curry2(L.findLast); 159 | 160 | export const indexOf: typeof L.indexOf & 161 | ((element: A) => (l: List) => number) = curry2(L.indexOf); 162 | 163 | export const lastIndexOf: typeof indexOf = curry2(L.lastIndexOf); 164 | 165 | export const findIndex: typeof L.findIndex & 166 | ((predicate: (a: A) => boolean) => (l: List) => number) = curry2( 167 | L.findIndex 168 | ); 169 | 170 | export const includes: typeof L.includes & 171 | ((element: A) => (l: List) => number) = curry2(L.includes); 172 | 173 | export const contains = includes; 174 | 175 | export const equals: typeof L.equals & 176 | ((first: List) => (second: List) => boolean) = curry2(L.equals); 177 | 178 | export const concat: typeof L.concat & 179 | ((left: List) => (right: List) => List) = curry2(L.concat); 180 | 181 | export const take: typeof L.take & 182 | ((n: number) => (l: List) => List) = curry2(L.take); 183 | 184 | export const takeLast: typeof take = curry2(L.takeLast); 185 | 186 | export const drop: typeof take = curry2(L.drop); 187 | 188 | export const dropRepeatsWith: typeof L.dropRepeatsWith & 189 | ((f: (a: A, b: A) => boolean) => (l: List) => List) = curry2( 190 | L.groupWith 191 | ); 192 | 193 | export const dropLast: typeof take = curry2(L.dropLast); 194 | 195 | export const takeWhile: typeof filter = curry2(L.takeWhile); 196 | 197 | export const takeLastWhile: typeof filter = curry2(L.takeLastWhile); 198 | 199 | export const dropWhile: typeof filter = curry2(L.dropWhile); 200 | 201 | export const splitAt: typeof L.splitAt & 202 | ((index: number) => (l: List) => [List, List]) = curry2( 203 | L.splitAt 204 | ); 205 | 206 | export const splitWhen: typeof L.splitWhen & 207 | (( 208 | predicate: (a: A) => boolean 209 | ) => (l: List) => [List, List]) = curry2(L.splitWhen); 210 | 211 | export const splitEvery: typeof L.splitEvery & 212 | ((size: number) => (l: List) => List>) = curry2(L.splitEvery); 213 | 214 | export const sortBy: typeof L.sortBy & 215 | (( 216 | f: (a: A) => B 217 | ) => (l: List) => List) = curry2(L.sortBy); 218 | 219 | export const sortWith: typeof L.sortWith & 220 | (( 221 | comparator: (a: A, b: A) => L.Ordering 222 | ) => (l: List) => List) = curry2(L.sortWith); 223 | 224 | export const groupWith: typeof L.groupWith & 225 | ((f: (a: A, b: A) => boolean) => (l: List) => List>) = curry2( 226 | L.groupWith 227 | ); 228 | 229 | export const zip: typeof L.zip & 230 | ((as: List) => (bs: List) => List<[A, B]>) = curry2(L.zip); 231 | 232 | export const sequence: typeof L.sequence & 233 | ((ofObj: L.Of) => (l: List>) => any) = curry2(L.sequence); 234 | 235 | // Arity 3 236 | 237 | export const foldl: typeof L.foldl & { 238 | (f: (acc: B, value: A) => B): Curried2, B>; 239 | (f: (acc: B, value: A) => B, initial: B): (l: List) => B; 240 | } = curry3(L.foldl); 241 | 242 | export const reduce: typeof foldl = foldl; 243 | 244 | export const scan: typeof L.scan & { 245 | (f: (acc: B, value: A) => B): Curried2, List>; 246 | (f: (acc: B, value: A) => B, initial: B): (l: List) => List; 247 | } = curry3(L.scan); 248 | 249 | export const foldr: typeof L.foldl & { 250 | (f: (value: A, acc: B) => B): Curried2, B>; 251 | (f: (value: A, acc: B) => B, initial: B): (l: List) => B; 252 | } = curry3(L.foldr); 253 | 254 | export const traverse: typeof L.traverse & { 255 | (of: L.Of): ((f: (a: A) => L.Applicative) => (l: List) => any) & 256 | ((f: (a: A) => L.Applicative, l: List) => any); 257 | (of: L.Of, f: (a: A) => L.Applicative): (l: List) => any; 258 | } = curry3(L.traverse); 259 | 260 | export const equalsWith: typeof L.equalsWith & { 261 | (f: (a: A, b: A) => boolean): Curried2, List, boolean>; 262 | (f: (a: A, b: A) => boolean, l1: List): (l2: List) => boolean; 263 | } = curry3(L.equalsWith); 264 | 265 | export const reduceRight: typeof foldr = foldr; 266 | 267 | export const update: typeof L.update & { 268 | (index: number, a: A): (l: List) => List; 269 | (index: number): ((a: A, l: List) => List) & 270 | ((a: A) => (l: List) => List); 271 | } = curry3(L.update); 272 | 273 | export const adjust: typeof L.adjust & { 274 | (index: number, f: (value: A) => A): (l: List) => List; 275 | (index: number): ( 276 | f: (value: A) => A, 277 | l: List 278 | ) => List & ((f: (value: A) => A) => (l: List) => List); 279 | } = curry3(L.adjust); 280 | 281 | export const slice: typeof L.slice & { 282 | (from: number): ((to: number) => (l: List) => List) & 283 | ((to: number, l: List) => List); 284 | (from: number, to: number): (l: List) => List; 285 | } = curry3(L.slice); 286 | 287 | export const remove: typeof slice = curry3(L.remove); 288 | 289 | export const insert: typeof update = curry3(L.insert); 290 | 291 | export const insertAll: typeof L.insertAll & { 292 | (index: number, elements: List): (l: List) => List; 293 | (index: number): ((elements: List, l: List) => List) & 294 | ((elements: List) => (l: List) => List); 295 | } = curry3(L.insertAll); 296 | 297 | export const zipWith: typeof L.zipWith & { 298 | (f: (a: A, b: B) => C, as: List): (bs: List) => List; 299 | (f: (a: A, b: B) => C): Curried2, List, List>; 300 | } = curry3(L.zipWith); 301 | 302 | // Arity 4 303 | 304 | export const foldlWhile: typeof L.foldlWhile & { 305 | // Three arguments 306 | ( 307 | predicate: (acc: B, value: A) => boolean, 308 | f: (acc: B, value: A) => B, 309 | initial: B 310 | ): (l: List) => B; 311 | // Two arguments 312 | ( 313 | predicate: (acc: B, value: A) => boolean, 314 | f: (acc: B, value: A) => B 315 | ): Curried2, B>; 316 | // One argument 317 | (predicate: (acc: B, value: A) => boolean): Curried3< 318 | (acc: B, value: A) => B, 319 | B, 320 | List, 321 | B 322 | >; 323 | } = curry4(L.foldlWhile); 324 | 325 | export const reduceWhile: typeof foldlWhile = foldlWhile; 326 | -------------------------------------------------------------------------------- /src/devtools.ts: -------------------------------------------------------------------------------- 1 | import * as L from "./index"; 2 | 3 | // The code below creates custom object formatter that works in Chrome 4 | // DevTools. 5 | // https://docs.google.com/document/d/1FTascZXT9cxfetuPRT2eXPQKXui4nWFivUnS_335T3U 6 | 7 | const listStyle = { 8 | style: 9 | "list-style-type: none; padding: 0; margin: 0 0 0 22px; font-style: normal" 10 | }; 11 | 12 | // @ts-ignore 13 | const gw = typeof window === undefined ? global : window; 14 | 15 | if (gw.devtoolsFormatters === undefined) { 16 | gw.devtoolsFormatters = []; 17 | } 18 | 19 | function createReference(object: any): any { 20 | return ["object", { object }]; 21 | } 22 | 23 | const l: L.List = L.list("hello", "world"); 24 | L.intersperse(", ", l); 25 | 26 | gw.devtoolsFormatters.push({ 27 | header: (l: any) => { 28 | if (L.isList(l)) { 29 | return [ 30 | "div", 31 | { style: "font-style: italic" }, 32 | ["span", {}, `(${l.length}) list(`], 33 | ...L.toArray(L.intersperse(", ", L.map(createReference, l))), 34 | ["span", {}, `)`] 35 | ]; 36 | } else { 37 | return null; 38 | } 39 | }, 40 | hasBody: (l: L.List) => { 41 | return l.length !== 0; 42 | }, 43 | body: (l: L.List) => { 44 | let idx = 0; 45 | const children = L.map( 46 | (o: any) => ["li", ["span", {}, idx++ + ": "], createReference(o)], 47 | l 48 | ); 49 | return ["ol", listStyle, ...L.toArray(children)]; 50 | } 51 | }); 52 | -------------------------------------------------------------------------------- /src/fantasy-land.ts: -------------------------------------------------------------------------------- 1 | import { 2 | List, 3 | equals, 4 | map, 5 | filter, 6 | empty, 7 | concat, 8 | foldl, 9 | of, 10 | ap, 11 | chain, 12 | traverse, 13 | Applicative, 14 | Of 15 | } from "./index"; 16 | 17 | export * from "./index"; 18 | 19 | const flOf = "fantasy-land/of"; 20 | const flEmpty = "fantasy-land/empty"; 21 | 22 | declare module "./index" { 23 | interface List { 24 | "fantasy-land/equals"(l: List): boolean; 25 | "fantasy-land/map"(f: (a: A) => B): List; 26 | "fantasy-land/of"(b: B): List; 27 | "fantasy-land/ap"(f: List<(a: A) => B>): List; 28 | "fantasy-land/chain"(f: (a: A) => List): List; 29 | "fantasy-land/filter"(predicate: (a: A) => boolean): List; 30 | "fantasy-land/empty"(): List; 31 | "fantasy-land/concat"(right: List): List; 32 | "fantasy-land/reduce"(f: (acc: B, value: A) => B, initial: B): B; 33 | "fantasy-land/traverse"(of: Of, f: (a: A) => Applicative): any; 34 | } 35 | } 36 | 37 | List.prototype["fantasy-land/equals"] = function(l: List): boolean { 38 | return equals(this, l); 39 | }; 40 | 41 | List.prototype["fantasy-land/map"] = function(f: (a: A) => B): List { 42 | return map(f, this); 43 | }; 44 | 45 | List.prototype[flOf] = of; 46 | (List as any)[flOf] = List.prototype[flOf]; 47 | 48 | List.prototype["fantasy-land/ap"] = function( 49 | listF: List<(a: A) => B> 50 | ): List { 51 | return ap(listF, this); 52 | }; 53 | 54 | List.prototype["fantasy-land/chain"] = function( 55 | f: (a: A) => List 56 | ): List { 57 | return chain(f, this); 58 | }; 59 | 60 | List.prototype["fantasy-land/filter"] = function( 61 | predicate: (a: A) => boolean 62 | ): List { 63 | return filter(predicate, this); 64 | }; 65 | 66 | List.prototype[flEmpty] = function(): List { 67 | return empty(); 68 | }; 69 | 70 | (List as any)[flEmpty] = List.prototype[flEmpty]; 71 | 72 | List.prototype["fantasy-land/concat"] = function(right: List): List { 73 | return concat(this, right); 74 | }; 75 | 76 | List.prototype["fantasy-land/reduce"] = function( 77 | f: (acc: B, value: A) => B, 78 | initial: B 79 | ): B { 80 | return foldl(f, initial, this); 81 | }; 82 | 83 | List.prototype["fantasy-land/traverse"] = function( 84 | of: Of, 85 | f: (a: A) => Applicative 86 | ): any { 87 | return traverse(of, f, this); 88 | }; 89 | -------------------------------------------------------------------------------- /src/methods.ts: -------------------------------------------------------------------------------- 1 | import { List, Comparable, Ordering, Applicative, Of } from "./fantasy-land"; 2 | import * as L from "./fantasy-land"; 3 | 4 | export * from "./index"; 5 | 6 | declare module "./index" { 7 | interface List { 8 | empty(): List; 9 | of(b: B): List; 10 | append(value: A): List; 11 | nth(index: number): A | undefined; 12 | prepend(value: A): List; 13 | append(value: A): List; 14 | intersperse(separator: A): List; 15 | first(): A | undefined; 16 | head(): A | undefined; 17 | last(): A | undefined; 18 | map(f: (a: A) => B): List; 19 | pluck(key: K): List; 20 | foldl(f: (acc: B, value: A) => B, initial: B): B; 21 | reduce(f: (acc: B, value: A) => B, initial: B): B; 22 | scan(f: (acc: B, value: A) => B, initial: B): List; 23 | foldr(f: (value: A, acc: B) => B, initial: B): B; 24 | reduceRight(f: (value: A, acc: B) => B, initial: B): B; 25 | foldlWhile( 26 | predicate: (acc: B, value: A) => boolean, 27 | f: (value: A, acc: B) => B, 28 | initial: B 29 | ): B; 30 | reduceWhile( 31 | predicate: (acc: B, value: A) => boolean, 32 | f: (value: A, acc: B) => B, 33 | initial: B 34 | ): B; 35 | traverse(of: Of, f: (a: A) => Applicative): any; 36 | sequence(this: List>, of: Of): any; 37 | forEach(callback: (a: A) => void): void; 38 | filter(predicate: (a: A) => boolean): List; 39 | filter(predicate: (a: A) => a is B): List; 40 | reject(predicate: (a: A) => boolean): List; 41 | partition(predicate: (a: A) => boolean): [List, List]; 42 | join(separator: string): string; 43 | ap(listF: List<(a: A) => B>): List; 44 | flatten(this: List>): List; 45 | flatMap(f: (a: A) => List): List; 46 | chain(f: (a: A) => List): List; 47 | every(predicate: (a: A) => boolean): boolean; 48 | some(predicate: (a: A) => boolean): boolean; 49 | none(predicate: (a: A) => boolean): boolean; 50 | indexOf(element: A): number; 51 | lastIndexOf(element: A): number; 52 | find(predicate: (a: A) => boolean): A | undefined; 53 | findLast(predicate: (a: A) => boolean): A | undefined; 54 | findIndex(predicate: (a: A) => boolean): number; 55 | includes(element: A): boolean; 56 | equals(secondList: List): boolean; 57 | equalsWith(f: (a: A, b: A) => boolean, secondList: List): boolean; 58 | concat(right: List): List; 59 | update(index: number, a: A): List; 60 | adjust(index: number, f: (a: A) => A): List; 61 | slice(from: number, to: number): List; 62 | take(n: number): List; 63 | takeWhile(predicate: (a: A) => boolean): List; 64 | takeLastWhile(predicate: (a: A) => boolean): List; 65 | takeLast(n: number): List; 66 | splitAt(index: number): [List, List]; 67 | splitWhen(predicate: (a: A) => boolean): [List, List]; 68 | splitEvery(size: number): List>; 69 | remove(from: number, amount: number): List; 70 | drop(n: number): List; 71 | dropWhile(predicate: (a: A) => boolean): List; 72 | dropRepeats(): List; 73 | dropRepeatsWith(predicate: (a: A, b: A) => boolean): List; 74 | dropLast(n: number): List; 75 | pop(): List; 76 | tail(): List; 77 | toArray(): A[]; 78 | insert(index: number, element: A): List; 79 | insertAll(index: number, elements: List): List; 80 | reverse(): List; 81 | backwards(): Iterable; 82 | zipWith(f: (a: A, b: B) => C, bs: List): List; 83 | zip(bs: List): List<[A, B]>; 84 | sort(this: List, l: List): List; 85 | sortBy(f: (a: A) => B): List; 86 | sortWith(comparator: (a: A, b: A) => Ordering): List; 87 | group(): List>; 88 | groupWith(f: (a: A, b: A) => boolean): List>; 89 | isEmpty(): boolean; 90 | } 91 | } 92 | 93 | List.prototype.append = function(value: A): List { 94 | return L.append(value, this); 95 | }; 96 | 97 | List.prototype.intersperse = function(separator: A): List { 98 | return L.intersperse(separator, this); 99 | }; 100 | 101 | List.prototype.nth = function(index: number): A | undefined { 102 | return L.nth(index, this); 103 | }; 104 | 105 | List.prototype.empty = function(): List { 106 | return L.empty(); 107 | }; 108 | 109 | List.prototype.of = function(b: B): List { 110 | return L.of(b); 111 | }; 112 | 113 | List.prototype.prepend = function(value: A): List { 114 | return L.prepend(value, this); 115 | }; 116 | 117 | List.prototype.append = function(value: A): List { 118 | return L.append(value, this); 119 | }; 120 | 121 | List.prototype.first = function(): A | undefined { 122 | return L.first(this); 123 | }; 124 | 125 | List.prototype.head = List.prototype.first; 126 | 127 | List.prototype.last = function(): A | undefined { 128 | return L.last(this); 129 | }; 130 | 131 | List.prototype.map = function(f: (a: A) => B): List { 132 | return L.map(f, this); 133 | }; 134 | 135 | List.prototype.pluck = function( 136 | this: List, 137 | key: K 138 | ): List { 139 | return L.pluck(key, this); 140 | } as any; 141 | 142 | List.prototype.foldl = function foldl( 143 | f: (acc: B, value: A) => B, 144 | initial: B 145 | ): B { 146 | return L.foldl(f, initial, this); 147 | }; 148 | 149 | List.prototype.reduce = List.prototype.foldl; 150 | 151 | List.prototype.scan = function scan( 152 | f: (acc: B, value: A) => B, 153 | initial: B 154 | ): List { 155 | return L.scan(f, initial, this); 156 | }; 157 | 158 | List.prototype.foldr = function( 159 | f: (value: A, acc: B) => B, 160 | initial: B 161 | ): B { 162 | return L.foldr(f, initial, this); 163 | }; 164 | 165 | List.prototype.reduceRight = List.prototype.foldr; 166 | 167 | List.prototype.foldlWhile = function foldlWhile( 168 | predicate: (acc: B, value: A) => boolean, 169 | f: (acc: B, value: A) => B, 170 | initial: B 171 | ): B { 172 | return L.foldlWhile(predicate, f, initial, this); 173 | }; 174 | 175 | List.prototype.reduceWhile = List.prototype.foldlWhile; 176 | 177 | List.prototype.traverse = function( 178 | of: Of, 179 | f: (a: A) => Applicative 180 | ): any { 181 | return L.traverse(of, f, this); 182 | }; 183 | 184 | List.prototype.sequence = function(this: List>, of: Of): any { 185 | return L.sequence(of, this); 186 | }; 187 | 188 | List.prototype.forEach = function(callback: (a: A) => void): void { 189 | return L.forEach(callback, this); 190 | }; 191 | 192 | List.prototype.filter = function(predicate: (a: A) => boolean): List { 193 | return L.filter(predicate, this); 194 | }; 195 | 196 | List.prototype.reject = function(predicate: (a: A) => boolean): List { 197 | return L.reject(predicate, this); 198 | }; 199 | 200 | List.prototype.partition = function( 201 | predicate: (a: A) => boolean 202 | ): [List, List] { 203 | return L.partition(predicate, this); 204 | }; 205 | 206 | List.prototype.join = function(separator: string): string { 207 | return L.join(separator, this); 208 | }; 209 | 210 | List.prototype.ap = function(listF: List<(a: A) => B>): List { 211 | return L.ap(listF, this); 212 | }; 213 | 214 | List.prototype.flatten = function(this: List>): List { 215 | return L.flatten(this); 216 | }; 217 | 218 | List.prototype.flatMap = function(f: (a: A) => List): List { 219 | return L.flatMap(f, this); 220 | }; 221 | 222 | List.prototype.chain = List.prototype.flatMap; 223 | 224 | List.prototype.every = function(predicate: (a: A) => boolean): boolean { 225 | return L.every(predicate, this); 226 | }; 227 | 228 | List.prototype.some = function(predicate: (a: A) => boolean): boolean { 229 | return L.some(predicate, this); 230 | }; 231 | 232 | List.prototype.none = function(predicate: (a: A) => boolean): boolean { 233 | return L.none(predicate, this); 234 | }; 235 | 236 | List.prototype.indexOf = function(element: A): number { 237 | return L.indexOf(element, this); 238 | }; 239 | 240 | List.prototype.lastIndexOf = function(element: A): number { 241 | return L.lastIndexOf(element, this); 242 | }; 243 | 244 | List.prototype.find = function find( 245 | predicate: (a: A) => boolean 246 | ): A | undefined { 247 | return L.find(predicate, this); 248 | }; 249 | 250 | List.prototype.findLast = function findLast( 251 | predicate: (a: A) => boolean 252 | ): A | undefined { 253 | return L.findLast(predicate, this); 254 | }; 255 | 256 | List.prototype.findIndex = function(predicate: (a: A) => boolean): number { 257 | return L.findIndex(predicate, this); 258 | }; 259 | 260 | List.prototype.includes = function(element: A): boolean { 261 | return L.includes(element, this); 262 | }; 263 | 264 | List.prototype.equals = function(secondList: List): boolean { 265 | return L.equals(this, secondList); 266 | }; 267 | 268 | List.prototype.equalsWith = function( 269 | f: (a: A, b: A) => boolean, 270 | secondList: List 271 | ): boolean { 272 | return L.equalsWith(f, this, secondList); 273 | }; 274 | 275 | List.prototype.concat = function(right: List): List { 276 | return L.concat(this, right); 277 | }; 278 | List.prototype.update = function(index: number, a: A): List { 279 | return L.update(index, a, this); 280 | }; 281 | List.prototype.adjust = function(index: number, f: (a: A) => A): List { 282 | return L.adjust(index, f, this); 283 | }; 284 | List.prototype.slice = function(from: number, to: number): List { 285 | return L.slice(from, to, this); 286 | }; 287 | 288 | List.prototype.take = function(n: number): List { 289 | return L.take(n, this); 290 | }; 291 | 292 | List.prototype.takeWhile = function(predicate: (a: A) => boolean): List { 293 | return L.takeWhile(predicate, this); 294 | }; 295 | 296 | List.prototype.takeLast = function(n: number): List { 297 | return L.takeLast(n, this); 298 | }; 299 | 300 | List.prototype.takeLastWhile = function( 301 | predicate: (a: A) => boolean 302 | ): List { 303 | return L.takeLastWhile(predicate, this); 304 | }; 305 | 306 | List.prototype.splitAt = function(index: number): [List, List] { 307 | return L.splitAt(index, this); 308 | }; 309 | 310 | List.prototype.splitWhen = function( 311 | predicate: (a: A) => boolean 312 | ): [List, List] { 313 | return L.splitWhen(predicate, this); 314 | }; 315 | 316 | List.prototype.splitEvery = function(size: number): List> { 317 | return L.splitEvery(size, this); 318 | }; 319 | 320 | List.prototype.remove = function(from: number, amount: number): List { 321 | return L.remove(from, amount, this); 322 | }; 323 | 324 | List.prototype.drop = function(n: number): List { 325 | return L.drop(n, this); 326 | }; 327 | 328 | List.prototype.dropWhile = function(predicate: (a: A) => boolean): List { 329 | return L.dropWhile(predicate, this); 330 | }; 331 | 332 | List.prototype.dropRepeats = function(): List { 333 | return L.dropRepeats(this); 334 | }; 335 | 336 | List.prototype.dropRepeatsWith = function( 337 | predicate: (a: A, b: A) => boolean 338 | ): List { 339 | return L.dropRepeatsWith(predicate, this); 340 | }; 341 | 342 | List.prototype.dropLast = function(n: number): List { 343 | return L.dropLast(n, this); 344 | }; 345 | List.prototype.pop = function(): List { 346 | return L.pop(this); 347 | }; 348 | 349 | List.prototype.tail = function(): List { 350 | return L.tail(this); 351 | }; 352 | 353 | List.prototype.toArray = function(): A[] { 354 | return L.toArray(this); 355 | }; 356 | 357 | List.prototype.insert = function(index: number, element: A): List { 358 | return L.insert(index, element, this); 359 | }; 360 | 361 | List.prototype.insertAll = function( 362 | index: number, 363 | elements: List 364 | ): List { 365 | return L.insertAll(index, elements, this); 366 | }; 367 | 368 | List.prototype.reverse = function(): List { 369 | return L.reverse(this); 370 | }; 371 | 372 | List.prototype.backwards = function(): Iterable { 373 | return L.backwards(this); 374 | }; 375 | 376 | List.prototype.zipWith = function( 377 | f: (a: A, b: B) => C, 378 | bs: List 379 | ): List { 380 | return L.zipWith(f, this, bs); 381 | }; 382 | List.prototype.zip = function(bs: List): List<[A, B]> { 383 | return L.zip(this, bs); 384 | }; 385 | 386 | List.prototype.sort = function(): List { 387 | return L.sort(this); 388 | }; 389 | 390 | List.prototype.sortWith = function( 391 | comparator: (a: A, b: A) => Ordering 392 | ): List { 393 | return L.sortWith(comparator, this); 394 | }; 395 | 396 | List.prototype.sortBy = function( 397 | f: (a: A) => B 398 | ): List { 399 | return L.sortBy(f, this); 400 | }; 401 | 402 | List.prototype.group = function(): List> { 403 | return L.group(this); 404 | }; 405 | 406 | List.prototype.groupWith = function( 407 | f: (a: A, b: A) => boolean 408 | ): List> { 409 | return L.groupWith(f, this); 410 | }; 411 | 412 | List.prototype.isEmpty = function(): boolean { 413 | return L.isEmpty(this); 414 | }; 415 | -------------------------------------------------------------------------------- /src/ramda.ts: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import { curry, CurriedFunction2 } from "ramda"; 3 | import * as L from "./index"; 4 | 5 | export { Node, List, list } from "./index"; 6 | 7 | export const prepend = curry(L.prepend); 8 | export const isList = curry(L.isList); 9 | export const append = curry(L.append); 10 | export const of = curry(L.of); 11 | export const pair = curry(L.pair); 12 | export const empty = curry(L.empty); 13 | export const repeat = curry(L.repeat); 14 | export const times = curry(L.times); 15 | export const length = curry(L.length); 16 | export const first = curry(L.first); 17 | export const head = first; 18 | export const last = curry(L.last); 19 | export const nth = curry(L.nth); 20 | export const map = curry(L.map); 21 | export const forEach = curry(L.forEach); 22 | export const pluck = curry(L.pluck as ((key: string, l: L.List) => A)); 23 | export const range = curry(L.range); 24 | export const foldl = curry(L.foldl); 25 | export const reduce = foldl; 26 | export const filter = curry(L.filter); 27 | export const reject = curry(L.reject); 28 | export const partition = curry(L.partition); 29 | export const join = curry(L.join); 30 | export const foldr = curry(L.foldr); 31 | export const reduceRight = foldr; 32 | export const ap = curry(L.ap); 33 | export const chain = curry(L.chain); 34 | export const flatten = curry(L.flatten); 35 | export const every = curry(L.every); 36 | export const all = every; 37 | export const some = curry(L.some); 38 | export const any = some; 39 | export const none = curry(L.none); 40 | export const find = curry(L.find); 41 | export const indexOf = curry(L.indexOf); 42 | export const findIndex = curry(L.findIndex); 43 | export const includes = curry(L.includes); 44 | export const contains = includes; 45 | export const equals = curry(L.equals); 46 | export const concat = curry(L.concat); 47 | export const update = curry(L.update); 48 | export const adjust = curry(L.adjust); 49 | export const slice = curry(L.slice); 50 | export const take = curry(L.take); 51 | export const takeWhile = curry(L.takeWhile); 52 | export const dropWhile = curry(L.dropWhile); 53 | export const takeLast = curry(L.takeLast); 54 | export const splitAt = curry(L.splitAt); 55 | export const remove = curry(L.remove); 56 | export const reverse = curry(L.reverse); 57 | export const drop = curry(L.drop); 58 | export const dropLast = curry(L.dropLast); 59 | export const pop = curry(L.pop); 60 | export const init = pop; 61 | export const tail = curry(L.tail); 62 | export const toArray = curry(L.toArray); 63 | export const from = curry(L.from); 64 | export const insert = curry(L.insert); 65 | export const insertAll = curry(L.insertAll); 66 | export const zip = curry(L.zip); 67 | export const zipWith = curry(L.zipWith); 68 | export const sort = curry(L.sort); 69 | export const sortWith = curry(L.sortWith); 70 | export const sortBy = curry(L.sortBy); 71 | -------------------------------------------------------------------------------- /test/bench/README.md: -------------------------------------------------------------------------------- 1 | # Benchmarks 2 | 3 | ## Running the benchmarks 4 | 5 | Clone the repository, install all the dependencies and build the 6 | library. 7 | 8 | ``` 9 | git clone https://github.com/funkia/list 10 | cd list 11 | npm install 12 | npm run build 13 | cd bench 14 | npm install 15 | ``` 16 | 17 | The benchmarks support comparing List against older versions of 18 | itself. Some secondary List version needs to be present for the 19 | benchamrks to run. 20 | 21 | ``` 22 | git clone https://github.com/funkia/list list-old 23 | cd list-old 24 | npm install 25 | npm run build 26 | ``` 27 | 28 | There are two groups of benchmarks. The first outputs data on the 29 | command line and the second generate graphs for viewing in a browser. 30 | 31 | Run the first group with. 32 | 33 | ``` 34 | node index.js 35 | ``` 36 | 37 | Generate the data for the graphs with. 38 | 39 | ``` 40 | npm run create-report -- run 41 | ``` 42 | 43 | And then view the results in you browser by openening the `index.html` file. 44 | 45 | ``` 46 | open index.html 47 | ``` 48 | 49 | Generating all the data for the benchmarks can take a long time (5-10 50 | minutes). If you're only interested in some of the results you can 51 | filter which benchmark cases are run and which libraries are tested. 52 | 53 | For instance to only run the concat and the foldl benchmark run this: 54 | 55 | ``` 56 | npm run create-report-data -- run -b concat foldl 57 | ``` 58 | 59 | To only compare List with and older version of itself you can run: 60 | 61 | ``` 62 | npm run create-report-data -- run -p list 63 | ``` 64 | 65 | The options can be combined. The below command will only run the 66 | concat benchmark and only test List and Lodash. 67 | 68 | ``` 69 | npm run create-report-data -- run -b concat -p list lodash 70 | ``` 71 | -------------------------------------------------------------------------------- /test/bench/append.suite.js: -------------------------------------------------------------------------------- 1 | const Suite = require("./default-suite").Suite; 2 | const Immutable = require("immutable"); 3 | const Denque = require("denque"); 4 | const mori = require("mori"); 5 | 6 | const Finger = require("@paldepind/finger-tree"); 7 | const { Cons } = require("./list"); 8 | const List = require("../../dist/index"); 9 | require("../../dist/methods"); 10 | const OldList = require("./list-old/dist/index"); 11 | 12 | const n = 100; 13 | 14 | module.exports = Suite("append") 15 | .add("Array", function() { 16 | let arr = []; 17 | for (let i = 0; i < n; ++i) { 18 | arr.push(i); 19 | } 20 | return arr.length === n; 21 | }) 22 | .add("Pure array", function() { 23 | let arr = []; 24 | for (let i = 0; i < n; ++i) { 25 | arr = arr.concat([i]); 26 | } 27 | return arr.length === n; 28 | }) 29 | .add("Immutable.js", function() { 30 | let list = new Immutable.List(); 31 | for (let i = 0; i < n; ++i) { 32 | list = list.push(i); 33 | } 34 | return list.size === n; 35 | }) 36 | .add("Denque", function() { 37 | let denque = new Denque(); 38 | for (let i = 0; i < n; ++i) { 39 | denque.push(i); 40 | } 41 | return denque.length === n; 42 | }) 43 | .add("mori", function() { 44 | let list = mori.vector(); 45 | for (let i = 0; i < n; ++i) { 46 | list = mori.conj(list, i); 47 | } 48 | return mori.count(list); 49 | }) 50 | .add("Cons", function() { 51 | let cons = undefined; 52 | for (let i = 0; i < n; ++i) { 53 | cons = new Cons(i, cons); 54 | } 55 | return cons.value === n - 1; 56 | }) 57 | .add("List", function() { 58 | let list = List.empty(); 59 | for (let i = 0; i < n; ++i) { 60 | list = list.append(i); 61 | } 62 | return list.length === n; 63 | }) 64 | .add("Old List", function() { 65 | let list = OldList.empty(); 66 | for (let i = 0; i < n; ++i) { 67 | list = list.append(i); 68 | } 69 | return list.length === n; 70 | }) 71 | .add("Finger", function() { 72 | let tree = Finger.nil; 73 | for (let i = 0; i < n; ++i) { 74 | tree = Finger.append(i, tree); 75 | } 76 | return tree.suffix.c === n - 1; 77 | }) 78 | .run({ async: true }); 79 | -------------------------------------------------------------------------------- /test/bench/appendtest.js: -------------------------------------------------------------------------------- 1 | const util = require("util"); 2 | const { nil, append, get } = require("@paldepind/finger-tree"); 3 | const shuffle = require("knuth-shuffle").knuthShuffle; 4 | 5 | const n = 10000; 6 | 7 | let tree = nil; 8 | let indices = []; 9 | 10 | for (let i = 0; i < n; ++i) { 11 | indices.push(i); 12 | tree = append(i, tree); 13 | } 14 | 15 | let sum = 0; 16 | 17 | for (let i = 0; i < n; ++i) { 18 | sum += get(i, tree); 19 | } 20 | 21 | shuffle(indices); 22 | 23 | console.log(sum); 24 | 25 | const l4 = append(3, append(2, append(1, append(0, nil)))); 26 | const s4 = get(0, l4) + get(1, l4) + get(2, l4) + get(3, l4); 27 | console.log(s4); 28 | 29 | function run() { 30 | for (var j = 0; j < 10000; ++j) { 31 | for (var i = 0; i < n; ++i) { 32 | sum += get(i, tree); 33 | } 34 | } 35 | } 36 | 37 | if (typeof document !== "undefined") { 38 | document.getElementById("start").addEventListener("click", function() { 39 | run(); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /test/bench/concat.perf.ts: -------------------------------------------------------------------------------- 1 | import { List } from "immutable"; 2 | import * as _ from "lodash"; 3 | import * as Finger from "@paldepind/finger-tree"; 4 | import { benchmark } from "./report"; 5 | 6 | import * as L from "../../dist/index"; 7 | import * as Lo from "./list-old/dist/index"; 8 | 9 | let left: any; 10 | let right: any; 11 | 12 | benchmark( 13 | { 14 | name: "concat", 15 | description: "Concatenates two sequences of size n.", 16 | input: [10, 50, 100, 250, 500, 1000, 5000, 10000] 17 | }, 18 | { 19 | List: { 20 | before: n => { 21 | left = L.range(0, n); 22 | right = L.range(n, 2 * n); 23 | }, 24 | run: () => L.concat(left, right) 25 | }, 26 | "List, old": { 27 | before: n => { 28 | left = Lo.range(0, n); 29 | right = Lo.range(n, 2 * n); 30 | }, 31 | run: () => Lo.concat(left, right) 32 | }, 33 | Lodash: { 34 | before: n => { 35 | left = _.range(0, n); 36 | right = _.range(n, 2 * n); 37 | }, 38 | run: () => _.concat(left, right) 39 | }, 40 | "Array#concat": { 41 | before: n => { 42 | left = _.range(0, n); 43 | right = _.range(n, 2 * n); 44 | }, 45 | run: () => left.concat(right) 46 | }, 47 | "Immutable.js": { 48 | before: n => { 49 | left = List(_.range(0, n)); 50 | right = List(_.range(n, 2 * n)); 51 | }, 52 | run: () => left.concat(right) 53 | }, 54 | Finger: { 55 | before: n => { 56 | left = Finger.nil; 57 | for (let i = 0; i < n; ++i) { 58 | left = Finger.append(i, left); 59 | } 60 | right = Finger.nil; 61 | for (let i = n; i < 2 * n; ++i) { 62 | right = Finger.append(i, right); 63 | } 64 | }, 65 | run: () => Finger.concat(left, right) 66 | } 67 | } 68 | ); 69 | -------------------------------------------------------------------------------- /test/bench/concat.suite.js: -------------------------------------------------------------------------------- 1 | const Suite = require("./default-suite").Suite; 2 | const Immutable = require("immutable"); 3 | const mori = require("mori"); 4 | 5 | const { nil, append, concat } = require("@paldepind/finger-tree"); 6 | const C = require("./list"); 7 | const List = require("../../dist/index"); 8 | require("../../dist/methods"); 9 | 10 | const n = 20000; 11 | 12 | let arrayA = []; 13 | let arrayB = []; 14 | let treeA = nil; 15 | let treeB = nil; 16 | let consA = undefined; 17 | let consB = undefined; 18 | let listA = List.empty(); 19 | let listB = List.empty(); 20 | 21 | for (let i = 0; i < n; ++i) { 22 | arrayA.push(i); 23 | arrayB.push(i); 24 | treeA = append(i, treeA); 25 | treeB = append(i, treeB); 26 | listA = listA.append(i); 27 | listB = listB.append(i); 28 | consA = new C.Cons(i, consA); 29 | consB = new C.Cons(i, consB); 30 | } 31 | let immutA = new Immutable.List(arrayA); 32 | let immutB = new Immutable.List(arrayB); 33 | 34 | module.exports = Suite("concat") 35 | .add("Array", function() { 36 | return arrayA.concat(arrayB).length; 37 | }) 38 | .add("Immutable.js", function() { 39 | return immutA.concat(immutB).size; 40 | }) 41 | .add("Cons-list", function() { 42 | return C.concat(consA, consB); 43 | }) 44 | .add("Finger", function() { 45 | return concat(treeA, treeB).size; 46 | }) 47 | .add("List", function() { 48 | return List.concat(listA, listB).length; 49 | }) 50 | .run({ async: true }); 51 | -------------------------------------------------------------------------------- /test/bench/default-suite.js: -------------------------------------------------------------------------------- 1 | var benchmark = require("benchmark"); 2 | 3 | function Suite(name) { 4 | return new benchmark.Suite(name) 5 | .on("cycle", function(e) { 6 | var t = e.target; 7 | if (t.failure) { 8 | console.error(padl(10, t.name) + "FAILED: " + e.target.failure); 9 | } else { 10 | var result = 11 | padl(30, t.name) + 12 | padr(13, t.hz.toFixed(2) + " op/s") + 13 | " \xb1" + 14 | padr(7, t.stats.rme.toFixed(2) + "%") + 15 | padr(15, " (" + t.stats.sample.length + " samples)"); 16 | console.log(result); 17 | } 18 | }) 19 | .on("start", function() { 20 | console.log("\n\n" + banner(67, this.name)); 21 | }) 22 | .on("complete", function() { 23 | console.log(banner(67, "Best: " + this.filter("fastest").map("name"))); 24 | }); 25 | } 26 | exports.Suite = Suite; 27 | 28 | function padl(n, s) { 29 | while (s.length < n) { 30 | s += " "; 31 | } 32 | return s; 33 | } 34 | 35 | function padr(n, s) { 36 | while (s.length < n) { 37 | s = " " + s; 38 | } 39 | return s; 40 | } 41 | 42 | function banner(n, s) { 43 | s = " " + s + " "; 44 | while (s.length < n) { 45 | s = "-" + s + "-"; 46 | } 47 | return s; 48 | } 49 | -------------------------------------------------------------------------------- /test/bench/filter.perf.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | 3 | import { benchmark } from "./report"; 4 | 5 | import * as Immutable from "immutable"; 6 | 7 | import * as Benchmark from "benchmark"; 8 | 9 | import * as L from "../../dist/index"; 10 | import * as Lo from "./list-old/dist/index"; 11 | 12 | function square(n: number): number { 13 | return n * n; 14 | } 15 | 16 | let array = []; 17 | let immut = Immutable.List(); 18 | let l = L.empty(); 19 | let lOld = Lo.empty(); 20 | 21 | function isEven(n: number): boolean { 22 | return n % 2 === 0; 23 | } 24 | 25 | // Dry run 26 | 27 | L.filter(n => n > 2, L.list(0, 1, 2, 3, 4)); 28 | 29 | benchmark( 30 | { 31 | name: "filter", 32 | description: "filters a sequence.", 33 | input: [20, 100, 500, 1000, 5000, 10000], 34 | before: n => { 35 | array = []; 36 | immut = Immutable.List(); 37 | l = L.empty(); 38 | lOld = Lo.empty(); 39 | 40 | for (let i = 0; i < n; ++i) { 41 | immut = immut.push(i); 42 | array.push(i); 43 | l = L.append(i, l); 44 | lOld = Lo.append(i, lOld); 45 | } 46 | } 47 | }, 48 | { 49 | List: { 50 | run: () => { 51 | return L.filter(isEven, l); 52 | } 53 | }, 54 | "List, old": { 55 | run: () => { 56 | return Lo.filter(isEven, lOld); 57 | } 58 | }, 59 | "Array#filter": { 60 | run: () => { 61 | return array.filter(isEven); 62 | } 63 | }, 64 | Lodash: { 65 | run: () => { 66 | return _.filter(array, isEven); 67 | } 68 | }, 69 | "Immutable.js": { 70 | run: () => { 71 | return immut.filter(isEven); 72 | } 73 | } 74 | } 75 | ); 76 | -------------------------------------------------------------------------------- /test/bench/foldl-iterator.perf.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from "./report"; 2 | 3 | const L = require("../../dist/index"); 4 | 5 | function subtract(n: number, m: number) { 6 | return n - m; 7 | } 8 | 9 | let l; 10 | 11 | benchmark( 12 | { 13 | name: "foldl vs iterator", 14 | description: "Iterating over a list with foldl vs with an iterator.", 15 | input: [100, 1000, 10000], 16 | before: n => { 17 | l = L.empty(); 18 | for (let i = 0; i < n; ++i) { 19 | l = L.append(i, l); 20 | } 21 | } 22 | }, 23 | { 24 | "List, foldl": () => { 25 | return L.foldl(subtract, 10, l); 26 | }, 27 | "List, iterator, for-of": () => { 28 | var result = 10; 29 | for (var cur of l) { 30 | result = subtract(result, cur); 31 | } 32 | return result; 33 | }, 34 | "List, iterator, manual": () => { 35 | var iterator = l[Symbol.iterator](); 36 | var result = 10; 37 | var cur; 38 | while ((cur = iterator.next()).done === false) { 39 | result = subtract(result, cur.value); 40 | } 41 | return result; 42 | } 43 | } 44 | ); 45 | -------------------------------------------------------------------------------- /test/bench/foldl.perf.ts: -------------------------------------------------------------------------------- 1 | const shuffle = require("knuth-shuffle").knuthShuffle; 2 | 3 | import * as _ from "lodash"; 4 | 5 | import { benchmark } from "./report"; 6 | 7 | import * as mori from "mori"; 8 | import * as Immutable from "immutable"; 9 | import * as Finger from "@paldepind/finger-tree"; 10 | 11 | import * as Benchmark from "benchmark"; 12 | 13 | import * as L from "../../dist/index"; 14 | import * as Lo from "./list-old/dist/index"; 15 | 16 | function arrayFold(f, initial: A, array: A[]): A { 17 | let value = initial; 18 | for (var i = 0; i < array.length; ++i) { 19 | value = f(value, array[i]); 20 | } 21 | return value; 22 | } 23 | 24 | arrayFold((n, m) => n + m, 0, [0, 1, 2, 3]); 25 | arrayFold((n, m) => n - m, 0, [0, 1, 2, 3]); 26 | L.foldl((n, m) => n + m, 0, L.list(0, 1, 2, 3)); 27 | L.foldl((n, m) => n - m, 0, L.list(0, 1, 2, 3)); 28 | Lo.foldl((n, m) => n + m, 0, Lo.list(0, 1, 2, 3)); 29 | Lo.foldl((n, m) => n - m, 0, Lo.list(0, 1, 2, 3)); 30 | 31 | function subtract(n: number, m: number) { 32 | return n - m; 33 | } 34 | 35 | let array = []; 36 | let tree = Finger.nil; 37 | let immut = Immutable.List(); 38 | let mlist = mori.vector(); 39 | let l = L.empty(); 40 | let lOld = Lo.empty(); 41 | 42 | benchmark( 43 | { 44 | name: "foldl", 45 | description: "foldl/reduce over a sequence.", 46 | input: [20, 100, 1000, 10000], 47 | before: n => { 48 | array = []; 49 | tree = Finger.nil; 50 | immut = Immutable.List(); 51 | mlist = mori.vector(); 52 | l = L.empty(); 53 | lOld = Lo.empty(); 54 | 55 | for (let i = 0; i < n; ++i) { 56 | tree = Finger.append(i, tree); 57 | immut = immut.push(i); 58 | mlist = mori.conj(mlist, i); 59 | array.push(i); 60 | l = L.append(i, l); 61 | lOld = Lo.append(i, lOld); 62 | } 63 | } 64 | }, 65 | { 66 | List: { 67 | run: () => { 68 | return L.foldl(subtract, 10, l); 69 | } 70 | }, 71 | "List, old": { 72 | run: () => { 73 | return Lo.foldl(subtract, 10, lOld); 74 | } 75 | }, 76 | "Array#reduce": { 77 | run: () => { 78 | return array.reduce(subtract, 10); 79 | } 80 | }, 81 | "Array manual fold": { 82 | run: () => { 83 | return arrayFold(subtract, 10, array); 84 | } 85 | }, 86 | Lodash: { 87 | run: () => { 88 | return _.reduce(array, subtract, 10); 89 | } 90 | }, 91 | "Immutable.js": { 92 | run: () => { 93 | return immut.reduce(subtract, 10); 94 | } 95 | }, 96 | Mori: { 97 | run: () => { 98 | return mori.reduce(subtract, 10, mlist); 99 | } 100 | }, 101 | Finger: { 102 | run: () => { 103 | return Finger.foldl(subtract, 10, tree); 104 | } 105 | } 106 | } 107 | ); 108 | -------------------------------------------------------------------------------- /test/bench/foldl.suite.js: -------------------------------------------------------------------------------- 1 | const Suite = require("./default-suite").Suite; 2 | const Immutable = require("immutable"); 3 | const Denque = require("denque"); 4 | const mori = require("mori"); 5 | const _ = require("lodash"); 6 | 7 | const Finger = require("@paldepind/finger-tree"); 8 | const { Cons } = require("./list"); 9 | 10 | const List = require("../../dist/index"); 11 | 12 | const n = 10000; 13 | 14 | let array = []; 15 | let tree = Finger.nil; 16 | let immut = new Immutable.List(); 17 | let mlist = mori.vector(); 18 | let list = List.empty(); 19 | 20 | for (let i = 0; i < n; ++i) { 21 | tree = Finger.append(i, tree); 22 | immut = immut.push(i); 23 | mlist = mori.conj(mlist, i); 24 | array.push(i); 25 | list = list.append(i); 26 | } 27 | 28 | function arrayFold(f, initial, array) { 29 | let value = initial; 30 | for (var i = 0; i < array.length; ++i) { 31 | value = f(value, array[i]); 32 | } 33 | return value; 34 | } 35 | 36 | function subtract(n, m) { 37 | return n - m; 38 | } 39 | 40 | module.exports = Suite("foldl") 41 | .add("Array", function() { 42 | return array.reduce(subtract, 10); 43 | }) 44 | .add("Array manual fold", function() { 45 | return arrayFold(subtract, 10, array); 46 | }) 47 | .add("Lodash", function() { 48 | return _.reduce(array, subtract, 10); 49 | }) 50 | .add("Immutable.js", function() { 51 | return immut.reduce(subtract, 10); 52 | }) 53 | .add("Mori", function() { 54 | return mori.reduce(subtract, 10, mlist); 55 | }) 56 | .add("Finger", function() { 57 | return Finger.foldl(subtract, 10, tree); 58 | }) 59 | .add("List", function() { 60 | return List.foldl(subtract, 10, list); 61 | }) 62 | .run({ async: true }); 63 | -------------------------------------------------------------------------------- /test/bench/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | List Benchmarks 7 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /test/bench/index.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | 3 | var benchmarks = fs.readdirSync(__dirname).filter(function(filename) { 4 | return filename.match(/\.suite\.js$/); 5 | }); 6 | 7 | console.log("Benchmarks found:"); 8 | benchmarks.forEach(function(file) { 9 | console.log("- " + file); 10 | }); 11 | 12 | function run(list) { 13 | function visit(length, i) { 14 | if (length > i) { 15 | require("./" + list[i]).on("complete", function() { 16 | visit(length, i + 1); 17 | }); 18 | } 19 | } 20 | visit(list.length, 0); 21 | } 22 | 23 | run(benchmarks); 24 | -------------------------------------------------------------------------------- /test/bench/index.ts: -------------------------------------------------------------------------------- 1 | import * as Plotly from "plotly.js/lib/core"; 2 | import * as _ from "lodash"; 3 | import * as R from "ramda"; 4 | import * as Benchmark from "benchmark"; 5 | import * as chroma from "chroma-js"; 6 | 7 | // @ts-ignore 8 | import * as data from "./data.json"; 9 | // @ts-ignore 10 | import * as tableView from "./view.handlebars"; 11 | 12 | const colormap = chroma 13 | .scale(["rgb(50, 213, 119)", "rgb(255, 84, 84)"]) 14 | .mode("hsl"); 15 | 16 | function scale(min: number, max: number, n: number): number { 17 | return (n - min) / (max - min); 18 | } 19 | 20 | function createDt(text: string) { 21 | const dt = document.createElement("td"); 22 | dt.textContent = text; 23 | return dt; 24 | } 25 | function createTh(text: string) { 26 | const dt = document.createElement("th"); 27 | dt.textContent = text; 28 | return dt; 29 | } 30 | 31 | type TableData = { 32 | name: string; 33 | description: string; 34 | input: any; 35 | tableRows: { 36 | name: string; 37 | data: { color: string; n: number }[]; 38 | }[]; 39 | }; 40 | 41 | function createTableData(plot): TableData { 42 | const getMean = R.path(["stats", "mean"]); 43 | const lowHigh: [number, number][] = R.pipe( 44 | R.pluck("result"), 45 | // @ts-ignore 46 | R.map(R.map(getMean)), 47 | R.transpose, 48 | R.map( 49 | R.reduce(([low, high], n: number) => [R.min(low, n), R.max(high, n)], [ 50 | Infinity, 51 | 0 52 | ]) 53 | ) 54 | )(plot.data); 55 | const tableRows = plot.data.map( 56 | (entry: { result: any; testName: string }) => { 57 | const data = _.map( 58 | _.zip(entry.result, lowHigh), 59 | ([r, [low, high]]) => { 60 | const color = colormap(scale(low, high, r.stats.mean)); 61 | const n = (r.stats.mean / low).toFixed(2); 62 | return { color, n, fastest: getMean(r) === low }; 63 | } 64 | ); 65 | return { 66 | name: entry.testName, 67 | data 68 | }; 69 | } 70 | ); 71 | return { 72 | name: plot.name, 73 | description: plot.description, 74 | tableRows, 75 | input: plot.input 76 | }; 77 | } 78 | 79 | function createData(data) { 80 | return { 81 | benchmarks: R.map(createTableData, data) 82 | }; 83 | } 84 | 85 | function plotData(name: string, ns: number[], stats: Benchmark[]): any { 86 | return { 87 | name: name, 88 | x: ns, 89 | y: stats.map(s => s.stats.mean), 90 | type: "scatter", 91 | error_y: { 92 | type: "data", 93 | visible: true, 94 | array: stats.map(s => s.stats.moe) 95 | }, 96 | text: stats.map(s => s.hz.toFixed(2) + " op/s") 97 | }; 98 | } 99 | 100 | function insertGraphs(): void { 101 | for (const plot of data) { 102 | const plotElm = document.createElement("div"); 103 | const input = plot.input; 104 | const dataForPlot = plot.data.map(({ testName, result }) => 105 | plotData(testName, input, result) 106 | ); 107 | const sortedData = _.reverse(_.sortBy(dataForPlot, d => _.last(d.y))); 108 | Plotly.plot( 109 | plotElm, 110 | sortedData, 111 | { 112 | yaxis: { 113 | title: "Time spent" 114 | }, 115 | xaxis: { 116 | title: "Number of elements" 117 | }, 118 | font: { 119 | size: 14, 120 | family: "'Source Sans Pro', sans-serif" 121 | }, 122 | // autosize: false, 123 | width: 600, 124 | height: 350, 125 | margin: { 126 | l: 65, 127 | r: 100, 128 | b: 40, 129 | t: 0 130 | }, 131 | paper_bgcolor: "transparent", 132 | plot_bgcolor: "transparent" 133 | }, 134 | { 135 | modeBarButtons: [ 136 | [ 137 | "zoom2d", 138 | "pan2d", 139 | "zoomIn2d", 140 | "zoomOut2d", 141 | "autoScale2d", 142 | "resetScale2d", 143 | "hoverClosestCartesian", 144 | "hoverCompareCartesian" 145 | ] 146 | ], 147 | displaylogo: false 148 | } 149 | ); 150 | document.getElementById(plot.name + "-graph").appendChild(plotElm); 151 | // document.body.appendChild(createTable(plot)); 152 | } 153 | } 154 | 155 | const div = document.createElement("div"); 156 | const viewData = createData(data); 157 | div.innerHTML = tableView(viewData); 158 | document.body.appendChild(div); 159 | 160 | insertGraphs(); 161 | -------------------------------------------------------------------------------- /test/bench/insert.perf.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from "./report"; 2 | import * as Immutable from "immutable"; 3 | import * as R from "ramda"; 4 | import * as mori from "mori"; 5 | 6 | import * as L from "../../dist/index"; 7 | import * as Lo from "./list-old/dist/index"; 8 | 9 | let idx = 0; 10 | 11 | let l; 12 | 13 | benchmark( 14 | { 15 | name: "insert", 16 | description: "Insert an element in the middle of a sequence.", 17 | input: [10, 50, 100, 250, 500, 1000, 5000, 10000] 18 | }, 19 | { 20 | List: { 21 | before: to => { 22 | l = L.range(0, to); 23 | idx = (to / 2) | 0; 24 | }, 25 | run: () => { 26 | const l1 = L.insert(idx, 0, l); 27 | } 28 | }, 29 | "List, old": { 30 | before: to => { 31 | l = Lo.range(0, to); 32 | idx = (to / 2) | 0; 33 | }, 34 | run: () => { 35 | const l1 = Lo.insert(idx, 0, l); 36 | } 37 | }, 38 | "Immutable.js": { 39 | before: to => { 40 | l = Immutable.Range(0, to).toList(); 41 | idx = (to / 2) | 0; 42 | }, 43 | run: () => { 44 | const l1 = l.insert(idx, 0); 45 | } 46 | }, 47 | Ramda: { 48 | before: to => { 49 | l = R.range(0, to); 50 | idx = (to / 2) | 0; 51 | }, 52 | run: () => { 53 | const l1 = R.insert(idx, 0, l); 54 | } 55 | } 56 | } 57 | ); 58 | -------------------------------------------------------------------------------- /test/bench/iterator.perf.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from "./report"; 2 | import * as R from "ramda"; 3 | import * as L from "../../dist/index"; 4 | import * as Lo from "./list-old/dist/index"; 5 | import * as Immutable from "immutable"; 6 | 7 | let n = 0; 8 | 9 | let l; 10 | let lOld; 11 | let immList; 12 | let array; 13 | 14 | benchmark( 15 | { 16 | name: "iterator", 17 | description: "Iterate over a sequence with a for-of loop.", 18 | input: [10, 50, 100, 250, 500, 1000, 5000], 19 | before: n => { 20 | l = L.range(0, n); 21 | lOld = Lo.range(0, n); 22 | immList = Immutable.Range(0, n).toList(); 23 | array = R.range(0, n); 24 | } 25 | }, 26 | { 27 | List: { 28 | run: () => { 29 | var result = 10000; 30 | for (var cur of l) { 31 | result = result - cur; 32 | } 33 | return result; 34 | } 35 | }, 36 | "List, old": { 37 | run: () => { 38 | var result = 10000; 39 | for (var cur of lOld) { 40 | result = result - cur; 41 | } 42 | return result; 43 | } 44 | }, 45 | "Immutable.js": { 46 | run: () => { 47 | var result = 10000; 48 | for (var cur of immList) { 49 | result = result - cur; 50 | } 51 | return result; 52 | } 53 | }, 54 | Array: { 55 | run: () => { 56 | var result = 10000; 57 | for (var cur of array) { 58 | result = result - cur; 59 | } 60 | return result; 61 | } 62 | } 63 | } 64 | ); 65 | -------------------------------------------------------------------------------- /test/bench/iterator.suite.js: -------------------------------------------------------------------------------- 1 | const Suite = require("./default-suite").Suite; 2 | const Immutable = require("immutable"); 3 | 4 | const List = require("../../dist/index"); 5 | const OldList = require("./list-old/dist/index"); 6 | 7 | const result = 1; 8 | 9 | const suite = Suite("Iterator"); 10 | 11 | function addBenchmark(n) { 12 | let array = []; 13 | let immut = new Immutable.List(); 14 | let list = List.empty(); 15 | let oldList = OldList.empty(); 16 | 17 | for (let i = 0; i < n; ++i) { 18 | list = list.append(i); 19 | oldList = oldList.append(i); 20 | array.push(i); 21 | immut = immut.push(i); 22 | } 23 | 24 | suite 25 | .add("Array " + n, function() { 26 | let sum = 0; 27 | for (const n of array) { 28 | sum += n; 29 | } 30 | return sum === result; 31 | }) 32 | .add("Immutable.js " + n, function() { 33 | let sum = 0; 34 | for (const n of immut) { 35 | sum += n; 36 | } 37 | return sum === result; 38 | }) 39 | .add("List " + n, function() { 40 | let sum = 0; 41 | for (const n of list) { 42 | sum += n; 43 | } 44 | return sum === result; 45 | }) 46 | .add("List old " + n, function() { 47 | let sum = 0; 48 | for (const n of oldList) { 49 | sum += n; 50 | } 51 | return sum === result; 52 | }); 53 | } 54 | 55 | addBenchmark(10); 56 | addBenchmark(50); 57 | addBenchmark(100); 58 | addBenchmark(1000); 59 | 60 | suite.run({ async: true }); 61 | 62 | module.exports = suite; 63 | -------------------------------------------------------------------------------- /test/bench/list.ts: -------------------------------------------------------------------------------- 1 | export class Cons { 2 | constructor(public value: A, public next: Cons | undefined) {} 3 | toArray(): A[] { 4 | const array = []; 5 | let cur: Cons | undefined = this; 6 | while (cur !== undefined) { 7 | array.push(cur.value); 8 | cur = cur.next; 9 | } 10 | return array; 11 | } 12 | nth(index: number): A | undefined { 13 | let cur: Cons | undefined = this; 14 | for (let i = 0; i < index && cur !== undefined; ++i) { 15 | cur = cur.next; 16 | } 17 | return cur === undefined ? undefined : cur.value; 18 | } 19 | } 20 | 21 | export function copyFirst(n: number, list: Cons): Cons { 22 | const newHead = new Cons(list.value, undefined); 23 | let current = list; 24 | let newCurrent = newHead; 25 | while (--n > 0) { 26 | current = current.next!; 27 | const cons = new Cons(current.value, undefined); 28 | newCurrent.next = cons; 29 | newCurrent = cons; 30 | } 31 | return newHead; 32 | } 33 | 34 | export function concat(a: Cons, b: Cons): Cons { 35 | let list = new Cons(a.value, undefined); 36 | let prev = list; 37 | let cur = a; 38 | while ((cur = cur.next!) !== undefined) { 39 | prev.next = new Cons(cur.value, undefined); 40 | prev = prev.next; 41 | } 42 | prev.next = b; 43 | return list; 44 | } 45 | -------------------------------------------------------------------------------- /test/bench/map.perf.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | 3 | import { benchmark } from "./report"; 4 | 5 | import * as Immutable from "immutable"; 6 | import * as Finger from "@paldepind/finger-tree"; 7 | 8 | import * as Benchmark from "benchmark"; 9 | 10 | import * as L from "../../dist/index"; 11 | import * as Lo from "./list-old/dist/index"; 12 | 13 | function square(n: number): number { 14 | return n * n; 15 | } 16 | 17 | let array = []; 18 | let tree = Finger.nil; 19 | let immut = Immutable.List(); 20 | let l = L.empty(); 21 | let lOld = Lo.empty(); 22 | 23 | benchmark( 24 | { 25 | name: "map", 26 | description: "maps a function over a sequence.", 27 | input: [20, 100, 500, 1000, 5000, 10000], 28 | before: n => { 29 | array = []; 30 | immut = Immutable.List(); 31 | l = L.empty(); 32 | lOld = Lo.empty(); 33 | 34 | for (let i = 0; i < n; ++i) { 35 | tree = Finger.append(i, tree); 36 | immut = immut.push(i); 37 | array.push(i); 38 | l = L.append(i, l); 39 | lOld = Lo.append(i, lOld); 40 | } 41 | } 42 | }, 43 | { 44 | List: { 45 | run: () => { 46 | return L.map(square, l); 47 | } 48 | }, 49 | "List, old": { 50 | run: () => { 51 | return Lo.map(square, lOld); 52 | } 53 | }, 54 | "Array#map": { 55 | run: () => { 56 | return array.map(square); 57 | } 58 | }, 59 | Lodash: { 60 | run: () => { 61 | return _.map(array, square); 62 | } 63 | }, 64 | "Immutable.js": { 65 | run: () => { 66 | return immut.map(square); 67 | } 68 | } 69 | } 70 | ); 71 | -------------------------------------------------------------------------------- /test/bench/map.suite.js: -------------------------------------------------------------------------------- 1 | const Suite = require("./default-suite").Suite; 2 | const Immutable = require("immutable"); 3 | const _ = require("lodash"); 4 | 5 | const List = require("../../dist/index"); 6 | const OldList = require("./list-old/dist/index"); 7 | 8 | const result = 1; 9 | 10 | const suite = Suite("map"); 11 | 12 | function square(n) { 13 | return n * n; 14 | } 15 | 16 | function addBenchmark(n) { 17 | let array = []; 18 | let immut = new Immutable.List(); 19 | let list = List.empty(); 20 | let oldList = OldList.empty(); 21 | 22 | for (let i = 0; i < n; ++i) { 23 | list = list.append(i); 24 | oldList = oldList.append(i); 25 | array.push(i); 26 | immut = immut.push(i); 27 | } 28 | 29 | suite 30 | .add("Array#map " + n, function() { 31 | return array.map(square); 32 | }) 33 | .add("Lodash " + n, function() { 34 | return _.map(array, square); 35 | }) 36 | .add("Immutable.js " + n, function() { 37 | return immut.map(square); 38 | }) 39 | .add("mapArray " + n, function() { 40 | return List.mapArray(square, array); 41 | }) 42 | .add("List " + n, function() { 43 | return List.map(square, list); 44 | }); 45 | } 46 | 47 | addBenchmark(10); 48 | // addBenchmark(50); 49 | // addBenchmark(100); 50 | addBenchmark(1000); 51 | 52 | suite.run({ async: true }); 53 | 54 | module.exports = suite; 55 | -------------------------------------------------------------------------------- /test/bench/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "benchmark", 3 | "private": true, 4 | "version": "0.0.1", 5 | "description": "Benchmarks.", 6 | "main": "index.js", 7 | "scripts": { 8 | "create-report": "ts-node report.ts" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "dependencies": { 13 | "@paldepind/finger-tree": "0.0.2", 14 | "@types/benchmark": "^1.0.31", 15 | "@types/lodash": "^4.14.108", 16 | "@types/node": "^10.0.4", 17 | "@types/plotly.js": "^1.36.0", 18 | "@types/pretty-ms": "^3.0.0", 19 | "@types/yargs": "^11.0.0", 20 | "benchmark": "^2.1.4", 21 | "chroma-js": "^1.3.7", 22 | "denque": "^1.2.3", 23 | "handlebars": "^4.7.7", 24 | "handlebars-loader": "^1.7.3", 25 | "immutable": "3.8.2", 26 | "knuth-shuffle": "^1.0.8", 27 | "lodash": "^4.17.19", 28 | "mori": "^0.3.2", 29 | "plotly.js": "^1.58.5", 30 | "pretty-ms": "^3.1.0", 31 | "ramda": "^0.25.0", 32 | "ts-loader": "^4.2.0", 33 | "ts-node": "^6.0.2", 34 | "webpack": "^4.7.0", 35 | "webpack-dev-server": "^3.1.4", 36 | "yargs": "^11.0.0" 37 | }, 38 | "devDependencies": { 39 | "webpack-cli": "^5.0.1" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/bench/prepare-benchmarks.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OLD_DIR="list-old" 4 | 5 | cd $(dirname $0) 6 | 7 | if [ ! -d "$OLD_DIR" ]; then 8 | git clone https://github.com/funkia/list "$OLD_DIR" 9 | cd "$OLD_DIR" 10 | npm install 11 | npm run build 12 | fi 13 | 14 | pwd; 15 | -------------------------------------------------------------------------------- /test/bench/prepend.perf.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from "./report"; 2 | import * as L from "../../dist/index"; 3 | import * as Lo from "./list-old/dist/index"; 4 | import * as _ from "lodash"; 5 | import * as Immutable from "immutable"; 6 | import * as R from "ramda"; 7 | import * as mori from "mori"; 8 | 9 | let n = 0; 10 | 11 | benchmark( 12 | { 13 | name: "append", 14 | description: 15 | "Constructs a sequence of size n by repeatedly appending to it.", 16 | input: [10, 50, 100, 250, 500, 1000, 5000, 10000] 17 | }, 18 | { 19 | List: { 20 | before: nn => { 21 | n = nn; 22 | }, 23 | run: () => { 24 | let list = L.empty(); 25 | for (let i = 0; i < n; ++i) { 26 | list = L.append(i, list); 27 | } 28 | return list.length === n; 29 | } 30 | }, 31 | "List, old": { 32 | before: nn => { 33 | n = nn; 34 | }, 35 | run: () => { 36 | let list = Lo.empty(); 37 | for (let i = 0; i < n; ++i) { 38 | list = Lo.append(i, list); 39 | } 40 | return list.length === n; 41 | } 42 | }, 43 | "Immutable.js": { 44 | before: nn => { 45 | n = nn; 46 | }, 47 | run: () => { 48 | let imm = Immutable.List(); 49 | for (let i = 0; i < n; ++i) { 50 | imm = imm.push(i); 51 | } 52 | return imm.size === n; 53 | } 54 | }, 55 | Ramda: { 56 | before: nn => { 57 | n = nn; 58 | }, 59 | run: () => { 60 | let arr: number[] = []; 61 | for (let i = 0; i < n; ++i) { 62 | arr = R.append(i, arr); 63 | } 64 | return arr.length === n; 65 | } 66 | }, 67 | Mori: { 68 | before: nn => { 69 | n = nn; 70 | }, 71 | run: () => { 72 | let list = mori.vector(); 73 | for (let i = 0; i < n; ++i) { 74 | list = mori.conj(list, i); 75 | } 76 | return mori.count(list); 77 | } 78 | } 79 | } 80 | ); 81 | 82 | benchmark( 83 | { 84 | name: "prepend", 85 | description: "Creates a sequence of size n by repeatedly prepending.", 86 | input: [10, 100, 500, 1000] 87 | }, 88 | { 89 | List: { 90 | before: nn => { 91 | n = nn; 92 | }, 93 | run: () => { 94 | let list = L.empty(); 95 | for (let i = 0; i < n; ++i) { 96 | list = L.prepend(i, list); 97 | } 98 | return list.length === n; 99 | } 100 | }, 101 | "Old list": { 102 | before: nn => { 103 | n = nn; 104 | }, 105 | run: () => { 106 | let list = Lo.empty(); 107 | for (let i = 0; i < n; ++i) { 108 | list = Lo.prepend(i, list); 109 | } 110 | return list.length === n; 111 | } 112 | } 113 | } 114 | ); 115 | -------------------------------------------------------------------------------- /test/bench/prepend.suite.js: -------------------------------------------------------------------------------- 1 | const Suite = require("./default-suite").Suite; 2 | const Immutable = require("immutable"); 3 | const Denque = require("denque"); 4 | 5 | const L = require("../../dist/index"); 6 | const Finger = require("@paldepind/finger-tree"); 7 | const { Cons } = require("./list"); 8 | 9 | const n = 10000; 10 | 11 | module.exports = Suite("prepend") 12 | .add("Array", function() { 13 | let arr = []; 14 | for (let i = 0; i < n; ++i) { 15 | arr.unshift(i); 16 | } 17 | return arr.length === n; 18 | }) 19 | .add("Pure array", function() { 20 | let arr = []; 21 | for (let i = 0; i < n; ++i) { 22 | arr = [i].concat(arr); 23 | } 24 | return arr.length === n; 25 | }) 26 | .add("Immutable.js", function() { 27 | let list = new Immutable.List(); 28 | for (let i = 0; i < n; ++i) { 29 | list = list.unshift(i); 30 | } 31 | return list.size === n; 32 | }) 33 | .add("Denque", function() { 34 | let denque = new Denque(); 35 | for (let i = 0; i < n; ++i) { 36 | denque.unshift(i); 37 | } 38 | return denque.length === n; 39 | }) 40 | .add("Cons", function() { 41 | let cons = undefined; 42 | for (let i = 0; i < n; ++i) { 43 | cons = new Cons(i, cons); 44 | } 45 | return cons.value === n - 1; 46 | }) 47 | .add("List", function() { 48 | let list = L.empty(); 49 | for (let i = 0; i < n; ++i) { 50 | list = L.prepend(i, list); 51 | } 52 | return list.length === n - 1; 53 | }) 54 | .add("Finger", function() { 55 | let tree = Finger.nil; 56 | for (let i = 0; i < n; ++i) { 57 | tree = Finger.prepend(i, tree); 58 | } 59 | return tree.suffix.c === n - 1; 60 | }) 61 | .run({ async: true }); 62 | -------------------------------------------------------------------------------- /test/bench/random-access.perf.ts: -------------------------------------------------------------------------------- 1 | const shuffle = require("knuth-shuffle").knuthShuffle; 2 | 3 | import * as _ from "lodash"; 4 | 5 | import { benchmark } from "./report"; 6 | 7 | import { List } from "immutable"; 8 | import * as Finger from "@paldepind/finger-tree"; 9 | 10 | import * as Benchmark from "benchmark"; 11 | 12 | import * as L from "../../dist/index"; 13 | import "../../dist/methods"; 14 | import * as Lo from "./list-old/dist/index"; 15 | import "./list-old/dist/methods"; 16 | 17 | let n: number; 18 | let list: L.List; 19 | let indices: number[] = []; 20 | let l: any; 21 | let imm: any; 22 | 23 | benchmark( 24 | { 25 | name: "random access", 26 | description: "Access each element in a sequence by index.", 27 | input: [50, 100, 1000, 5000, 10000], 28 | before: m => { 29 | // n = m; 30 | // for (let i = 0; i < n; ++i) { 31 | // indices.push(i); 32 | // } 33 | // shuffle(indices); 34 | } 35 | }, 36 | { 37 | List: { 38 | before: m => { 39 | n = m; 40 | l = L.empty(); 41 | for (let i = 0; i < n; ++i) { 42 | l = L.append(i, l); 43 | } 44 | }, 45 | run: () => { 46 | let sum = 0; 47 | for (let i = 0; i < n; ++i) { 48 | sum += l.nth(i); 49 | } 50 | return sum; 51 | } 52 | }, 53 | "List, old": { 54 | before: m => { 55 | n = m; 56 | l = Lo.empty(); 57 | for (let i = 0; i < n; ++i) { 58 | l = Lo.append(i, l); 59 | } 60 | }, 61 | run: () => { 62 | let sum = 0; 63 | for (let i = 0; i < n; ++i) { 64 | sum += l.nth(i); 65 | } 66 | return sum; 67 | } 68 | }, 69 | Array: { 70 | before: m => { 71 | n = m; 72 | l = []; 73 | for (let i = 0; i < n; ++i) { 74 | l.push(i); 75 | } 76 | }, 77 | run: () => { 78 | let sum = 0; 79 | for (let i = 0; i < n; ++i) { 80 | sum += l[i]; 81 | } 82 | return sum; 83 | } 84 | }, 85 | "Immutable.js": { 86 | before: m => { 87 | n = m; 88 | imm = List(); 89 | for (let i = 0; i < n; ++i) { 90 | imm = imm.push(i); 91 | } 92 | }, 93 | run: () => { 94 | let sum = 0; 95 | for (let i = 0; i < n; ++i) { 96 | sum += imm.get(i); 97 | } 98 | return sum; 99 | } 100 | } 101 | } 102 | ); 103 | -------------------------------------------------------------------------------- /test/bench/random-access.suite.js: -------------------------------------------------------------------------------- 1 | const shuffle = require("knuth-shuffle").knuthShuffle; 2 | const Suite = require("./default-suite").Suite; 3 | const Immutable = require("immutable"); 4 | const Denque = require("denque"); 5 | 6 | const List = require("../../dist/index"); 7 | require("../../dist/methods"); 8 | const OldList = require("./list-old/dist/index"); 9 | const Finger = require("@paldepind/finger-tree"); 10 | const { Cons } = require("./list"); 11 | 12 | const n = 10000; 13 | 14 | let indices = []; 15 | let array = []; 16 | let denque = new Denque(); 17 | let immut = new Immutable.List(); 18 | let tree = Finger.nil; 19 | let list = List.empty(); 20 | let oldList = OldList.empty(); 21 | 22 | for (let i = 0; i < n; ++i) { 23 | tree = Finger.append(i, tree); 24 | list = list.append(i); 25 | oldList = oldList.append(i); 26 | denque.push(i); 27 | array.push(i); 28 | indices.push(i); 29 | immut = immut.push(i); 30 | } 31 | 32 | const result = 49995000; 33 | 34 | shuffle(indices); 35 | 36 | module.exports = Suite("random access") 37 | .add("Array", function() { 38 | let sum = 0; 39 | for (let i = 0; i < n; ++i) { 40 | sum += array[i]; 41 | } 42 | return sum === result; 43 | }) 44 | .add("Immutable.js", function() { 45 | let sum = 0; 46 | for (let i = 0; i < n; ++i) { 47 | sum += immut.get(i); 48 | } 49 | return sum === result; 50 | }) 51 | .add("Deque", function() { 52 | let sum = 0; 53 | for (let i = 0; i < n; ++i) { 54 | sum += denque.peekAt(i); 55 | } 56 | return sum === result; 57 | }) 58 | .add("List", function() { 59 | let sum = 0; 60 | for (let i = 0; i < n; ++i) { 61 | sum += list.nth(i); 62 | } 63 | return sum === result; 64 | }) 65 | .add("Old list", function() { 66 | let sum = 0; 67 | for (let i = 0; i < n; ++i) { 68 | sum += oldList.nth(i); 69 | } 70 | return sum === result; 71 | }) 72 | .add("Finger", function() { 73 | let sum = 0; 74 | for (let i = 0; i < n; ++i) { 75 | sum += Finger.get(i, tree); 76 | } 77 | return sum === result; 78 | }) 79 | .run({ async: true }); 80 | -------------------------------------------------------------------------------- /test/bench/report.ts: -------------------------------------------------------------------------------- 1 | import * as util from "util"; 2 | import * as fs from "fs"; 3 | const writeFile = util.promisify(fs.writeFile); 4 | const webpack = require("webpack"); 5 | const webpackAsync = util.promisify(webpack); 6 | import prettyMs = require("pretty-ms"); 7 | import yargs = require("yargs"); 8 | 9 | import * as Benchmark from "benchmark"; 10 | 11 | const webpackConfig = require("./webpack.config"); 12 | 13 | function runAsync(benchmark: Benchmark) { 14 | return new Promise(resolve => { 15 | benchmark.on("complete", resolve).run(); 16 | }); 17 | } 18 | 19 | async function runTest( 20 | name: string, 21 | suite: Bench, 22 | test: Test, 23 | input: A[] 24 | ): Promise { 25 | const results = []; 26 | for (let i = 0; i < input.length; i++) { 27 | const n = input[i]; 28 | if (suite.before !== undefined) { 29 | suite.before(n); 30 | } 31 | if (test.before !== undefined) { 32 | test.before(n); 33 | } 34 | const b = new Benchmark(name + n.toString(), { 35 | fn: test.run 36 | }); 37 | b.on("complete", event => { 38 | console.log(`${i + 1}:${input.length}`, String(event.target)); 39 | }); 40 | await runAsync(b); 41 | results.push(b); 42 | } 43 | return results; 44 | } 45 | 46 | type Test = { 47 | before?: (input: Input) => void; 48 | run: () => void; 49 | }; 50 | 51 | type Tests = { [name: string]: Test | (() => void) }; 52 | 53 | type BenchmarkOptions = { 54 | name: string; 55 | description?: string; 56 | input?: Input[]; 57 | before?: (input: Input) => void; 58 | }; 59 | 60 | type Bench = { 61 | name: string; 62 | description?: string; 63 | tests: Tests; 64 | input?: Input[]; 65 | before?: (input: Input) => void; 66 | }; 67 | 68 | const benchmarks: Bench[] = []; 69 | 70 | export function benchmark( 71 | options: string | BenchmarkOptions, 72 | tests: Tests 73 | ): void { 74 | if (typeof options === "string") { 75 | options = { name: options }; 76 | } 77 | benchmarks.push(Object.assign({}, options, { tests })); 78 | } 79 | 80 | function areSubstrings(s: string, ss: string[]): boolean { 81 | return ss.some(s2 => s.toLowerCase().includes(s2.toLowerCase())); 82 | } 83 | 84 | async function runBenchmarks(argv: any): Promise { 85 | const { b: benchmarkNames, p, exP } = argv; 86 | (require)("./prepend.perf"); 87 | (require)("./concat.perf"); 88 | (require)("./map.perf"); 89 | (require)("./filter.perf"); 90 | (require)("./foldl.perf"); 91 | (require)("./slice.perf"); 92 | (require)("./random-access.perf"); 93 | (require)("./update.perf"); 94 | (require)("./insert.perf"); 95 | (require)("./iterator.perf"); 96 | (require)("./sort.perf"); 97 | (require)("./foldl-iterator.perf"); 98 | 99 | const startTime = Date.now(); 100 | const results = []; 101 | const relevantBenchmarks = 102 | benchmarkNames === undefined 103 | ? benchmarks 104 | : benchmarks.filter(({ name }) => areSubstrings(name, benchmarkNames)); 105 | console.log("Running", relevantBenchmarks.length, "benchmarks"); 106 | 107 | for (const suite of relevantBenchmarks) { 108 | const { name, description, input, tests } = suite; 109 | console.log("Running", name); 110 | const data = []; 111 | const names = Object.keys(tests); 112 | const names2 = 113 | p !== undefined ? names.filter(name => areSubstrings(name, p)) : names; 114 | const names3 = 115 | exP !== undefined 116 | ? names2.filter(name => !areSubstrings(name, exP)) 117 | : names2; 118 | for (let i = 0; i < names3.length; i++) { 119 | console.log(`${i} out of ${names3.length}`); 120 | const testName = names3[i]; 121 | const testData = tests[testName]; 122 | const test = 123 | typeof testData === "function" ? { run: testData } : testData; 124 | const result = await runTest(testName, suite, test, input); 125 | data.push({ testName, result }); 126 | } 127 | results.push({ name, description, input, data }); 128 | } 129 | 130 | await writeFile("data.json", JSON.stringify(results)); 131 | console.log("Generating bundle"); 132 | const stats = await webpackAsync(webpackConfig); 133 | if (stats.hasErrors()) { 134 | // Handle errors here 135 | console.log(stats.toString({ colors: true })); 136 | } 137 | const endTime = Date.now(); 138 | console.log("Done in ", prettyMs(endTime - startTime)); 139 | } 140 | 141 | // tslint:disable-next-line:no-unused-expression 142 | yargs 143 | .command( 144 | "run", 145 | "run the benchmarks", 146 | yargs => yargs, 147 | argv => { 148 | runBenchmarks(argv); 149 | } 150 | ) 151 | .option("b", { 152 | alias: "benchmarks", 153 | describe: 154 | "Filtering of benchmarks. Only run those that include one of the names.", 155 | type: "array" 156 | }) 157 | .option("p", { 158 | alias: "performers", 159 | describe: 160 | "Filtering of performers. Only run those that include one of the names.", 161 | type: "array" 162 | }) 163 | .option("exP", { 164 | alias: "excludePerformers", 165 | describe: "Exclude any performers that includes any of the given strings.", 166 | type: "array" 167 | }) 168 | .help().argv; 169 | -------------------------------------------------------------------------------- /test/bench/slice.perf.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from "./report"; 2 | import * as Immutable from "immutable"; 3 | import * as R from "ramda"; 4 | 5 | import * as L from "../../dist/index"; 6 | import * as Lo from "./list-old/dist/index"; 7 | 8 | let start = 0; 9 | let end = 0; 10 | 11 | let l; 12 | let lOld; 13 | 14 | benchmark( 15 | { 16 | name: "slice", 17 | description: "Slice 25% off of both ends of a sequence.", 18 | input: [10, 50, 100, 250, 500, 1000, 5000, 10000] 19 | }, 20 | { 21 | List: { 22 | before: to => { 23 | l = L.range(0, to); 24 | start = (to / 4) | 0; 25 | end = start * 3; 26 | }, 27 | run: () => { 28 | const l1 = L.slice(start, end, l); 29 | } 30 | }, 31 | "List, old": { 32 | before: to => { 33 | l = Lo.range(0, to); 34 | start = (to / 4) | 0; 35 | end = start * 3; 36 | }, 37 | run: () => { 38 | const l1 = Lo.slice(start, end, l); 39 | } 40 | }, 41 | "Immutable.js": { 42 | before: to => { 43 | l = Immutable.Range(0, to).toList(); 44 | start = (to / 4) | 0; 45 | end = start * 3; 46 | }, 47 | run: () => { 48 | const l1 = l.slice(start, end); 49 | } 50 | }, 51 | "Array#slice": { 52 | before: to => { 53 | l = R.range(0, to); 54 | start = (to / 4) | 0; 55 | end = start * 3; 56 | }, 57 | run: () => { 58 | const l1 = l.slice(start, end); 59 | } 60 | } 61 | } 62 | ); 63 | -------------------------------------------------------------------------------- /test/bench/sort.perf.ts: -------------------------------------------------------------------------------- 1 | const shuffle = require("knuth-shuffle").knuthShuffle; 2 | 3 | import * as R from "ramda"; 4 | 5 | import { benchmark } from "./report"; 6 | 7 | import { List } from "immutable"; 8 | 9 | import * as L from "../../dist/index"; 10 | import "../../dist/methods"; 11 | import * as Lo from "./list-old/dist/index"; 12 | import "./list-old/dist/methods"; 13 | 14 | let arr: number[]; 15 | let l: any; 16 | let imm: any; 17 | 18 | function copyArray(arr: A[]): A[] { 19 | const newArray = []; 20 | for (let i = 0; i < arr.length; ++i) { 21 | newArray.push(arr[i]); 22 | } 23 | return newArray; 24 | } 25 | 26 | function comparePrimitive(a: number, b: number): -1 | 0 | 1 { 27 | if (a === b) { 28 | return 0; 29 | } else if (a < b) { 30 | return -1; 31 | } else { 32 | return 1; 33 | } 34 | } 35 | 36 | function sortUnstable(l: L.List): L.List { 37 | return L.from(L.toArray(l).sort(comparePrimitive as any)); 38 | } 39 | 40 | benchmark( 41 | { 42 | name: "sort, numbers", 43 | description: "Sort a list of numbers", 44 | input: [50, 100, 1000, 5000, 10000, 50000], 45 | before: n => { 46 | arr = []; 47 | for (let i = 0; i < n; ++i) { 48 | arr.push(i); 49 | } 50 | shuffle(arr); 51 | } 52 | }, 53 | { 54 | Ramda: { 55 | run: () => { 56 | return R.sort(comparePrimitive, arr).length; 57 | } 58 | }, 59 | List: { 60 | before: _ => { 61 | l = L.from(arr); 62 | }, 63 | run: () => { 64 | return L.sort(l).length; 65 | } 66 | }, 67 | "List, old": { 68 | before: _ => { 69 | l = Lo.from(arr); 70 | }, 71 | run: () => { 72 | return Lo.sort(l).length; 73 | } 74 | }, 75 | Array: { 76 | run: () => { 77 | return copyArray(arr).sort().length; 78 | } 79 | }, 80 | "Array, with comparator": { 81 | run: () => { 82 | return copyArray(arr).sort(comparePrimitive).length; 83 | } 84 | }, 85 | "Immutable.js": { 86 | before: _ => { 87 | imm = List(arr); 88 | }, 89 | run: () => { 90 | return imm.sort().length; 91 | } 92 | } 93 | } 94 | ); 95 | 96 | benchmark( 97 | { 98 | name: "sort, stable vs unstable", 99 | description: "Compares the cost of doing a stable search", 100 | input: [50, 100, 1000, 5000, 10000, 50000], 101 | before: n => { 102 | arr = []; 103 | for (let i = 0; i < n; ++i) { 104 | arr.push(i); 105 | } 106 | shuffle(arr); 107 | } 108 | }, 109 | { 110 | List: { 111 | before: _ => { 112 | l = L.from(arr); 113 | }, 114 | run: () => { 115 | return L.sort(l).length; // Uses unstable sort since numbers 116 | } 117 | }, 118 | "List, stable": { 119 | before: _ => { 120 | l = L.from(arr); 121 | }, 122 | run: () => { 123 | return L.sortWith(comparePrimitive, l).length; // Uses stable sort 124 | } 125 | }, 126 | "Array#sort": { 127 | run: () => { 128 | return copyArray(arr).sort(comparePrimitive).length; 129 | } 130 | } 131 | } 132 | ); 133 | -------------------------------------------------------------------------------- /test/bench/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2016", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./dist", 7 | "sourceMap": true, 8 | "lib": ["es2015", "dom"] 9 | }, 10 | "include": [ 11 | "index.ts" 12 | ], 13 | "exclude": [ 14 | "node_modules" 15 | ] 16 | } -------------------------------------------------------------------------------- /test/bench/update.perf.ts: -------------------------------------------------------------------------------- 1 | import { benchmark } from "./report"; 2 | import * as Immutable from "immutable"; 3 | import * as R from "ramda"; 4 | import * as mori from "mori"; 5 | 6 | import * as L from "../../dist/index"; 7 | import * as Lo from "./list-old/dist/index"; 8 | 9 | let idx1 = 0; 10 | let idx2 = 0; 11 | let idx3 = 0; 12 | 13 | let l: any; 14 | 15 | benchmark( 16 | { 17 | name: "update", 18 | description: 19 | "Update elements at location 0.25, 0.5, and 0.75 i a sequence.", 20 | input: [10, 50, 100, 250, 500, 1000, 5000, 10000] 21 | }, 22 | { 23 | List: { 24 | before: to => { 25 | l = L.range(0, to); 26 | idx1 = (to / 4) | 0; 27 | idx2 = idx1 * 2; 28 | idx3 = idx1 * 3; 29 | }, 30 | run: () => { 31 | const l1 = L.update(idx1, 0, l); 32 | const l2 = L.update(idx2, 0, l1); 33 | const l3 = L.update(idx3, 0, l2); 34 | } 35 | }, 36 | "List, old": { 37 | before: to => { 38 | l = Lo.range(0, to); 39 | idx1 = (to / 4) | 0; 40 | idx2 = idx1 * 2; 41 | idx3 = idx1 * 3; 42 | }, 43 | run: () => { 44 | const l1 = Lo.update(idx1, 0, l); 45 | const l2 = Lo.update(idx2, 0, l1); 46 | const l3 = Lo.update(idx3, 0, l2); 47 | } 48 | }, 49 | "Immutable.js": { 50 | before: to => { 51 | l = Immutable.Range(0, to).toList(); 52 | idx1 = (to / 4) | 0; 53 | idx2 = idx1 * 2; 54 | idx3 = idx1 * 3; 55 | }, 56 | run: () => { 57 | const l1 = l.set(idx1, 0); 58 | const l2 = l1.set(idx2, 0); 59 | const l3 = l2.set(idx3, 0); 60 | } 61 | }, 62 | Ramda: { 63 | before: to => { 64 | l = R.range(0, to); 65 | idx1 = (to / 4) | 0; 66 | idx2 = idx1 * 2; 67 | idx3 = idx1 * 3; 68 | }, 69 | run: () => { 70 | const l1 = R.update(idx1, 0, l); 71 | const l2 = R.update(idx2, 0, l1); 72 | const l3 = R.update(idx3, 0, l2); 73 | } 74 | }, 75 | mori: { 76 | before: to => { 77 | l = mori.vector(); 78 | for (let i = 0; i < to; ++i) { 79 | l = mori.conj(l, i); 80 | } 81 | idx1 = (to / 4) | 0; 82 | idx2 = idx1 * 2; 83 | idx3 = idx1 * 3; 84 | }, 85 | run: () => { 86 | const l1 = mori.assoc(l, idx1, 0); 87 | const l2 = mori.assoc(l1, idx2, 0); 88 | const l3 = mori.assoc(l2, idx3, 0); 89 | } 90 | } 91 | } 92 | ); 93 | -------------------------------------------------------------------------------- /test/bench/view.handlebars: -------------------------------------------------------------------------------- 1 |
2 | 3 |

List benchmarks

4 |

5 | The tables shows the performance of the libraries in relation to 6 | the fastest. The numbers denote how many times slower than the 7 | fastest the library is. The graphs are interactive. Controls are 8 | revealed by hovering. 9 |

10 |

11 | Lower is better. 12 |

13 | 14 |

15 | GitHub repository - 16 | How to run the benchmarks 17 |

18 |
19 | {{#each benchmarks}} 20 |
21 |

{{name}}

22 | 23 | {{#if description}} 24 |

{{description}}

25 | {{/if}} 26 | 27 |
28 |
29 | 30 | 31 | 32 | {{#each input}} 33 | 34 | {{/each}} 35 | 36 | {{#each tableRows}} 37 | 38 | 39 | {{#each data}} 40 | 48 | {{/each}} 49 | 50 | {{/each}} 51 |
n{{this}}
{{name}} 41 | {{n}}x 42 | {{#if fastest}} 43 |
fastest
44 | {{else}} 45 |
slower
46 | {{/if}} 47 |
52 |
53 |
54 | {{/each}} 55 |
56 |
-------------------------------------------------------------------------------- /test/bench/webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: "./index.ts", 3 | mode: "development", 4 | module: { 5 | rules: [ 6 | { 7 | test: /\.tsx?$/, 8 | use: "ts-loader", 9 | exclude: /node_modules/ 10 | }, 11 | { test: /\.handlebars$/, loader: "handlebars-loader" } 12 | ] 13 | }, 14 | resolve: { 15 | extensions: [".tsx", ".ts", ".js"] 16 | }, 17 | output: { 18 | filename: "bundle.js", 19 | path: __dirname 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /test/check.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | 3 | import * as L from "../src/index"; 4 | import { List, Node } from "../src/index"; 5 | 6 | // These three getter functions are identical to the unexported functions in 7 | // `src/index.ts` with the same names. 8 | 9 | const affixBits = 6; 10 | const affixMask = 0b111111; 11 | 12 | function getSuffixSize(l: List): number { 13 | return l.bits & affixMask; 14 | } 15 | 16 | function getPrefixSize(l: List): number { 17 | return (l.bits >> affixBits) & affixMask; 18 | } 19 | 20 | function getDepth(l: List): number { 21 | return l.bits >> (affixBits * 2); 22 | } 23 | 24 | function computeDepth(node: any, direction: number): number { 25 | if (node && Array.isArray(node.array) && "sizes" in node) { 26 | // This is a node 27 | const path = Math.floor((node.array.length - 1) * direction); 28 | return 1 + computeDepth(node.array[path], direction); 29 | } else { 30 | return -1; 31 | } 32 | } 33 | 34 | function doComputeOffset( 35 | node: Node, 36 | depth: number, 37 | offset: number, 38 | leftSpine: boolean 39 | ): number { 40 | if (depth === 0) { 41 | return offset; 42 | } else { 43 | let newOffset = offset; 44 | if (node.sizes === undefined && leftSpine) { 45 | // Nodes with size-tables does not affect `offset`. In this case 46 | // the size tables store the information about how many elements 47 | // are to be found. 48 | const offsetHere = 32 - node.array.length; 49 | newOffset = offset | (offsetHere << (depth * 5)); 50 | } 51 | return doComputeOffset( 52 | node.array[0], 53 | depth - 1, 54 | newOffset, 55 | leftSpine || node.array.length !== 1 56 | ); 57 | } 58 | } 59 | 60 | function computeOffset(l: List): number { 61 | return doComputeOffset(l.root!, getDepth(l), 0, false); 62 | } 63 | 64 | /** 65 | * This functions checks that the given list satisfies all invariants. It also 66 | * performs a bunch of other sanity check to ensure that the given list is 67 | * constructed correctly. 68 | * 69 | * This functions also serves as documentation for the invariants. 70 | * 71 | * Throws if an error is found. 72 | */ 73 | export function checkList(l: List): void { 74 | const depth = getDepth(l); 75 | const prefixSize = getPrefixSize(l); 76 | const suffixSize = getSuffixSize(l); 77 | 78 | // Depth should be 0 if `l.root` is undefined. 79 | if (l.root === undefined) { 80 | assert.equal(depth, 0, "root was undefined but depth was not 0"); 81 | } else { 82 | // If a list has a tree (i.e. root is not undefined) then both the suffix and 83 | // the prefix must contain elements. 84 | assert.notEqual(suffixSize, 0, "tree exists but suffix is empty"); 85 | assert.notEqual(prefixSize, 0, "tree exists but prefix is empty"); 86 | assert.exists(l.prefix, "tree exists but suffix is empty"); 87 | assert.exists(l.suffix, "tree exists but prefix is empty"); 88 | 89 | // Check that the declared depth is equal to the depth found by manually 90 | // traversing the tree. 91 | const msg = "depth is incorrect"; 92 | assert.equal(depth, computeDepth(l.root, 0), msg); // Check the left-most path 93 | assert.equal(depth, computeDepth(l.root, 0.25), msg); 94 | assert.equal(depth, computeDepth(l.root, 0.5), msg); // Check the middle path 95 | assert.equal(depth, computeDepth(l.root, 0.75), msg); 96 | assert.equal(depth, computeDepth(l.root, 1), msg); // Check the right-most path 97 | 98 | // If the root node has size tables the size tables should be consistent with 99 | // the length of the list. 100 | if (l.root.sizes !== undefined) { 101 | const sizes = l.root.sizes[l.root.sizes.length - 1]; 102 | const calculated = prefixSize + suffixSize + sizes; 103 | assert.equal( 104 | l.length, 105 | calculated, 106 | `list has length ${ 107 | l.length 108 | } but prefixSize(${prefixSize}) + suffixSize(${suffixSize}) + size from size table(${sizes}) is ${calculated}` 109 | ); 110 | } 111 | } 112 | 113 | // The offset should be identical to the offset that we manually compute 114 | const computedOffset = computeOffset(l); 115 | assert.equal( 116 | l.offset, 117 | computedOffset, 118 | `expected offset to be ${computedOffset} but it was ${l.offset}` 119 | ); 120 | } 121 | 122 | export function installCheck(library: any): any { 123 | const newLibrary: any = {}; 124 | for (const name of Object.keys(library)) { 125 | if (typeof library[name] === "function") { 126 | const fn = library[name]; 127 | newLibrary[name] = (...args: any[]) => { 128 | const result = fn(...args); 129 | if (L.isList(result)) { 130 | // This is a list, apply checks 131 | checkList(result); 132 | } 133 | return result; 134 | }; 135 | } else { 136 | newLibrary[name] = library[name]; 137 | } 138 | } 139 | return newLibrary; 140 | } 141 | -------------------------------------------------------------------------------- /test/commands.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import * as fc from "fast-check"; 3 | import * as L from "../src"; 4 | 5 | type Real = { data: L.List }; 6 | type Model = { data: number[] }; 7 | 8 | export class ConcatCommand implements fc.Command { 9 | constructor(readonly source: number[]) {} 10 | check(/*m: Readonly*/): boolean { 11 | return true; 12 | } 13 | run(m: Model, r: Real): void { 14 | m.data = [...m.data, ...this.source]; 15 | r.data = L.concat(r.data, L.from(this.source)); 16 | assert.deepEqual(L.toArray(r.data), m.data); 17 | } 18 | toString(): string { 19 | return `L.concat(src, L.from([${this.source.join(",")}]))`; 20 | } 21 | } 22 | 23 | export class DropCommand implements fc.Command { 24 | constructor(readonly num: number) {} 25 | check(/*m: Readonly*/): boolean { 26 | return true; 27 | } 28 | run(m: Model, r: Real): void { 29 | m.data = m.data.slice(this.num); 30 | r.data = L.drop(this.num, r.data); 31 | assert.deepEqual(L.toArray(r.data), m.data); 32 | } 33 | toString(): string { 34 | return `L.drop(${this.num}, src)`; 35 | } 36 | } 37 | 38 | export class TakeCommand implements fc.Command { 39 | constructor(readonly num: number) {} 40 | check(/*m: Readonly*/): boolean { 41 | return true; 42 | } 43 | run(m: Model, r: Real): void { 44 | m.data = m.data.slice(0, this.num); 45 | r.data = L.take(this.num, r.data); 46 | assert.deepEqual(L.toArray(r.data), m.data); 47 | } 48 | toString(): string { 49 | return `L.take(${this.num}, src)`; 50 | } 51 | } 52 | 53 | export class MapCommand implements fc.Command { 54 | constructor(readonly val: number) {} 55 | check(/*m: Readonly*/): boolean { 56 | return true; 57 | } 58 | run(m: Model, r: Real): void { 59 | m.data = m.data.map(v => (v * this.val) | 0); 60 | r.data = L.map(v => (v * this.val) | 0, r.data); 61 | assert.deepEqual(L.toArray(r.data), m.data); 62 | } 63 | toString(): string { 64 | return `L.map(v => (v * ${this.val}) | 0, src)`; 65 | } 66 | } 67 | 68 | export class ReverseCommand implements fc.Command { 69 | constructor() {} 70 | check(/*m: Readonly*/): boolean { 71 | return true; 72 | } 73 | run(m: Model, r: Real): void { 74 | m.data = m.data.reverse(); 75 | r.data = L.reverse(r.data); 76 | assert.deepEqual(L.toArray(r.data), m.data); 77 | } 78 | toString(): string { 79 | return `L.reverse(src)`; 80 | } 81 | } 82 | 83 | export class ConcatPreCommand implements fc.Command { 84 | constructor(readonly source: number[]) {} 85 | check(/*m: Readonly*/): boolean { 86 | return true; 87 | } 88 | run(m: Model, r: Real): void { 89 | m.data = [...this.source, ...m.data]; 90 | r.data = L.concat(L.from(this.source), r.data); 91 | assert.deepEqual(L.toArray(r.data), m.data); 92 | } 93 | toString(): string { 94 | return `L.concat(L.from([${this.source.join(",")}]), src)`; 95 | } 96 | } 97 | 98 | export class FilterCommand implements fc.Command { 99 | constructor(readonly val: number) {} 100 | check(/*m: Readonly*/): boolean { 101 | return true; 102 | } 103 | run(m: Model, r: Real): void { 104 | m.data = m.data.filter(v => v >= this.val); 105 | r.data = L.filter(v => v >= this.val, r.data); 106 | assert.deepEqual(L.toArray(r.data), m.data); 107 | } 108 | toString(): string { 109 | return `L.filter(v => v >= ${this.val}, src)`; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /test/curried.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | 3 | import * as L from "../src/index"; 4 | import * as Lc from "../src/curried"; 5 | import { assertListEqual } from "."; 6 | 7 | describe("curried", () => { 8 | it("has all functions", () => { 9 | for (const key of Object.keys(L)) { 10 | const elm = (L as any)[key]; 11 | // Check all non-anonymous functions 12 | if (typeof elm === "function") { 13 | assert.isTrue(key in Lc, `Missing key ${key}`); 14 | assert.strictEqual( 15 | (L as any)[key].length, 16 | (Lc as any)[key].length, 17 | `Incorrect arity for ${key}` 18 | ); 19 | } 20 | } 21 | }); 22 | it("has curried map", () => { 23 | const l = Lc.map((n: number) => n * n)(Lc.list(1, 2, 3, 4)); 24 | assert.isTrue(Lc.equals(l, Lc.list(1, 4, 9, 16))); 25 | }); 26 | it("partially applied nth can be called on lists of different types", () => { 27 | const snd = Lc.nth(1); 28 | const one = snd(Lc.list(0, 1, 2, 3)); 29 | assert.strictEqual(one, 1); 30 | const b = snd(Lc.list("a", "b", "c", "d")); 31 | assert.strictEqual(b, "b"); 32 | }); 33 | it("returns pluck with correct type", () => { 34 | const pluckFoo = Lc.pluck("foo"); 35 | const l = pluckFoo(Lc.list({ foo: 0 }, { foo: 1 }, { foo: 2 })); 36 | assert.isTrue(Lc.equals(l, Lc.list(0, 1, 2))); 37 | // Should not give type error since `l` is list of `number` 38 | assert.equal(Lc.foldl((a: number, b: number) => a + b, 0, l), 3); 39 | }); 40 | it("curried filter works with user-defined type guards", () => { 41 | function pred(a: any): a is string { 42 | return typeof a === "string"; 43 | } 44 | const filterPred = Lc.filter(pred); 45 | const l = L.list(0, "one", 2, "three", 4, "five"); 46 | const l2 = filterPred(l); 47 | const l3 = L.map(s => s[0], l2); 48 | assertListEqual(l3, L.list("o", "t", "f")); 49 | }); 50 | it("can call foldl in all combinations", () => { 51 | const f = (sum: number, s: string) => sum + s.length; 52 | const l = Lc.list("hi ", "there"); 53 | const r1 = Lc.foldl(f, 0, l); 54 | const r2 = Lc.foldl(f)(0, l); 55 | const r4 = Lc.foldl(f, 0)(l); 56 | const r3 = Lc.foldl(f)(0)(l); 57 | assert.strictEqual(r1, 3 + 5); 58 | assert.strictEqual(r2, 3 + 5); 59 | assert.strictEqual(r3, 3 + 5); 60 | assert.strictEqual(r4, 3 + 5); 61 | }); 62 | it("can call slice in all combinations", () => { 63 | const l = Lc.list(0, 1, 2, 3, 4, 5); 64 | const l1 = Lc.slice(1, 5, l); 65 | const l2 = Lc.slice(1)(5)(l); 66 | const l3 = Lc.slice(1, 5)(l); 67 | const l4 = Lc.slice(1)(5, l); 68 | assert.isTrue(Lc.equals(l1, Lc.list(1, 2, 3, 4))); 69 | assert.isTrue(Lc.equals(l2, Lc.list(1, 2, 3, 4))); 70 | assert.isTrue(Lc.equals(l3, Lc.list(1, 2, 3, 4))); 71 | assert.isTrue(Lc.equals(l4, Lc.list(1, 2, 3, 4))); 72 | }); 73 | it("supports reduceWhile in all combinations", () => { 74 | const sum = (a: number, b: number) => a + b; 75 | const isOdd = (_acc: any, x: number) => x % 2 === 1; 76 | const xs = Lc.list(1, 3, 5, 60, 777, 800); 77 | const a = Lc.foldlWhile(isOdd, sum, 0, xs); 78 | const b = Lc.foldlWhile(isOdd, sum, 0)(xs); 79 | const c = Lc.foldlWhile(isOdd, sum)(0, xs); 80 | const d = Lc.foldlWhile(isOdd, sum)(0)(xs); 81 | const e = Lc.foldlWhile(isOdd)(sum, 0, xs); 82 | const f = Lc.foldlWhile(isOdd)(sum, 0)(xs); 83 | const g = Lc.foldlWhile(isOdd)(sum)(0, xs); 84 | const h = Lc.foldlWhile(isOdd)(sum)(0)(xs); 85 | assert.strictEqual(a, 9); 86 | assert.strictEqual(b, 9); 87 | assert.strictEqual(c, 9); 88 | assert.strictEqual(d, 9); 89 | assert.strictEqual(e, 9); 90 | assert.strictEqual(f, 9); 91 | assert.strictEqual(g, 9); 92 | assert.strictEqual(h, 9); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /test/fantasy-land.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | 3 | import * as L from "../src/index"; 4 | import * as Lf from "../src/fantasy-land"; 5 | import { just, nothing, isJust, Maybe } from "./utils"; 6 | 7 | describe("Fantasy Land", () => { 8 | it("has all functions", () => { 9 | for (const key of Object.keys(L)) { 10 | const elm = (L as any)[key]; 11 | // Check all non-anonymous functions 12 | if (typeof elm === "function" && elm.name === key) { 13 | assert.isTrue(key in Lf); 14 | } 15 | } 16 | }); 17 | describe("setoid", () => { 18 | it("has Fantasy Land method", () => { 19 | assert.isTrue( 20 | L.list(0, 1, 2, 3, 4)["fantasy-land/equals"](L.list(0, 1, 2, 3, 4)) 21 | ); 22 | }); 23 | }); 24 | describe("monoid", () => { 25 | it("has fantasy land empty", () => { 26 | L.list(0, 1, 2)["fantasy-land/empty"](); 27 | }); 28 | it("has fantasy land concat", () => { 29 | L.list(0, 1, 2)["fantasy-land/concat"](L.list(3, 4)); 30 | }); 31 | }); 32 | describe("monad", () => { 33 | it("has map method", () => { 34 | const n = 50; 35 | const l = L.range(0, n); 36 | const mapped = l["fantasy-land/map"](m => m * m); 37 | for (let i = 0; i < n; ++i) { 38 | assert.strictEqual(L.nth(i, mapped), i * i); 39 | } 40 | }); 41 | it("has ap method", () => { 42 | const l = L.list(1, 2, 3)["fantasy-land/ap"]( 43 | L.list((n: number) => n + 2, (n: number) => 2 * n, (n: number) => n * n) 44 | ); 45 | assert.isTrue(L.equals(l, L.list(3, 4, 5, 2, 4, 6, 1, 4, 9))); 46 | }); 47 | it("has chain method", () => { 48 | const l = L.list(1, 2, 3); 49 | const l2 = l["fantasy-land/chain"](n => L.list(n, 2 * n, n * n)); 50 | assert.isTrue(L.equals(l2, L.list(1, 2, 1, 2, 4, 4, 3, 6, 9))); 51 | }); 52 | }); 53 | describe("traversable", () => { 54 | it("has reduce method", () => { 55 | const l = L.list(0, 1, 2, 3, 4, 5); 56 | const result = l["fantasy-land/reduce"]((arr, i) => (arr.push(i), arr), < 57 | number[] 58 | >[]); 59 | assert.deepEqual(result, [0, 1, 2, 3, 4, 5]); 60 | }); 61 | it("has traverse method", () => { 62 | const safeDiv = (n: number) => (d: number) => 63 | d === 0 ? nothing : just(n / d); 64 | const r = L.list(2, 4, 5)["fantasy-land/traverse"](Maybe, safeDiv(10)); 65 | assert.isTrue(isJust(r) && L.equals(r.val, L.list(5, 2.5, 2))); 66 | }); 67 | }); 68 | describe("filterable", () => { 69 | it("has Fantasy Land method", () => { 70 | assert.strictEqual( 71 | L.list(0, 1, 2, 3, 4, 5)["fantasy-land/filter"](n => n % 2 === 0) 72 | .length, 73 | 3 74 | ); 75 | }); 76 | }); 77 | }); 78 | -------------------------------------------------------------------------------- /test/methods.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | 3 | import * as L from "../src/index"; 4 | import * as Lm from "../src/methods"; 5 | 6 | const K: typeof L = undefined as any; 7 | 8 | const exceptions = [ 9 | "Node", 10 | "List", 11 | "list", 12 | "pair", 13 | "repeat", 14 | "times", 15 | "range", 16 | "from", 17 | "isList" 18 | ]; 19 | 20 | describe("methods", () => { 21 | it("has all functions", () => { 22 | const l = Lm.list(0); 23 | for (const key of Object.keys(L)) { 24 | const k: keyof (typeof K) = key as any; 25 | const elm = L[k]; 26 | // Check all non-anonymous functions 27 | if ( 28 | typeof elm === "function" && 29 | elm.name === k && 30 | exceptions.indexOf(elm.name) === -1 31 | ) { 32 | assert.isTrue(key in l, `missing function ${key}`); 33 | } 34 | } 35 | }); 36 | it("can chain", () => { 37 | const l = Lm.list( 38 | { name: "Foo bar" }, 39 | { name: "Foo baz" }, 40 | { name: "Alan Turing" }, 41 | { name: "Haskell Curry" } 42 | ); 43 | const l2 = l 44 | .pluck("name") 45 | .filter(n => n[0] === "F") 46 | .take(2); 47 | assert.equal(l2.nth(0), "Foo bar"); 48 | assert.equal(l2.nth(1), "Foo baz"); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /test/property/index.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | 3 | import { 4 | concat, 5 | empty, 6 | List, 7 | prepend, 8 | append, 9 | nth, 10 | slice 11 | } from "../../src/index"; 12 | 13 | // function numberArray(start: number, end: number): number[] { 14 | // let array = []; 15 | // for (let i = start; i < end; ++i) { 16 | // array.push(i); 17 | // } 18 | // return array; 19 | // } 20 | 21 | /** 22 | * Call the body n number of times. 23 | */ 24 | function times(n: number, body: (n: number) => void): void { 25 | for (let i = 0; i < n; ++i) { 26 | body(i); 27 | } 28 | } 29 | 30 | function appendList(start: number, end: number, l = empty()): List { 31 | for (let i = start; i < end; ++i) { 32 | l = append(i, l); 33 | } 34 | return l; 35 | } 36 | 37 | function prependList(start: number, end: number, l = empty()): List { 38 | for (let i = end - 1; i >= start; --i) { 39 | l = prepend(i, l); 40 | } 41 | return l; 42 | } 43 | 44 | /** 45 | * Returns a random integer between min (inclusive) and max (inclusive) 46 | * Using Math.round() will give you a non-uniform distribution! 47 | */ 48 | function randomInInterval(min: number, max: number) { 49 | return Math.floor(Math.random() * (max - min + 1)) + min; 50 | } 51 | 52 | // @ts-ignore 53 | function randomBool() { 54 | return Math.random() > 0.5; 55 | } 56 | 57 | function assertIndicesFromTo( 58 | list: List, 59 | from: number, 60 | to: number, 61 | offset: number = 0 62 | ): void { 63 | for (let i = 0; i < to - from; ++i) { 64 | assert.strictEqual(nth(i + offset, list), from + i); 65 | } 66 | } 67 | 68 | function cheapAssertIndicesFromTo( 69 | list: List, 70 | from: number, 71 | to: number 72 | ): void { 73 | const length = to - from; 74 | if (length > 100) { 75 | assertIndicesFromTo(list, from, from + 50); 76 | assertIndicesFromTo(list, to - 50, to, length - 50); 77 | const middle = Math.floor(length / 2); 78 | assertIndicesFromTo(list, from + middle, from + middle + 1, middle); 79 | } else { 80 | assertIndicesFromTo(list, from, to); 81 | } 82 | } 83 | 84 | describe("properties", () => { 85 | it("concat", () => { 86 | times(1000, _i => { 87 | let sum = 0; 88 | const nrOfLists = 12; 89 | let l = empty(); 90 | const sizes = []; 91 | for (let j = 0; j < nrOfLists; ++j) { 92 | const size = randomInInterval(0, 32 ** 3); 93 | sizes.push(size); 94 | const list2 = appendList(sum, sum + size); 95 | sum += size; 96 | l = concat(l, list2); 97 | } 98 | cheapAssertIndicesFromTo(l, 0, sum); 99 | }); 100 | }); 101 | it("slice", () => { 102 | const size = 32000097; 103 | const l = appendList(0, size); 104 | times(10000, i => { 105 | let left: number; 106 | let right: number; 107 | if (i % 2 === 0) { 108 | left = randomInInterval(0, size); 109 | right = randomInInterval(left, size); 110 | } else { 111 | right = randomInInterval(0, size); 112 | left = randomInInterval(0, right); 113 | } 114 | try { 115 | const sliced = slice(left, right, l); 116 | cheapAssertIndicesFromTo(sliced, left, right); 117 | } catch (err) { 118 | console.log("Slice error: ", left, "to", right); 119 | throw err; 120 | } 121 | }); 122 | }); 123 | it("concat and prepend", () => { 124 | times(10000, _n => { 125 | try { 126 | const prependSize = 10000; 127 | const nrOfListsToConcat = randomInInterval(5, 25); 128 | 129 | let offset = prependSize; 130 | let concatenated = empty(); 131 | times(nrOfListsToConcat, () => { 132 | const size = randomInInterval(100, 1000); 133 | const list = prependList(offset, offset + size); 134 | // side-effects 135 | concatenated = concat(concatenated, list); 136 | offset += size; 137 | }); 138 | const final = prependList(0, prependSize, concatenated); 139 | cheapAssertIndicesFromTo(final, 0, offset); 140 | } catch (err) { 141 | console.log(_n); 142 | throw err; 143 | } 144 | }); 145 | }); 146 | it("concat and slice", () => { 147 | times(10000, _n => { 148 | const nrOfListsToConcat = 2; // randomInInterval(3, 14); 149 | 150 | let offset = 0; 151 | let concatenated = empty(); 152 | times(nrOfListsToConcat, () => { 153 | const size = randomInInterval(100, 10000); 154 | const list = prependList(offset, offset + size); 155 | // side-effects 156 | concatenated = concat(concatenated, list); 157 | offset += size; 158 | }); 159 | const a = randomInInterval(0, concatenated.length); 160 | const b = randomInInterval(0, concatenated.length); 161 | const from = Math.min(a, b); 162 | const to = Math.max(a, b); 163 | const final = slice(from, to, concatenated); 164 | try { 165 | cheapAssertIndicesFromTo(final, from, to); 166 | } catch (err) { 167 | console.log(_n); 168 | console.log(from); 169 | throw err; 170 | } 171 | }); 172 | }); 173 | }); 174 | -------------------------------------------------------------------------------- /test/ramda.ts: -------------------------------------------------------------------------------- 1 | import { assert } from "chai"; 2 | import * as R from "ramda"; 3 | 4 | import * as L from "../src/index"; 5 | // import * as Lr from "../src/ramda"; 6 | 7 | describe("Ramda", () => { 8 | // These tests are commented out since the Ramda module is deprecated 9 | /* 10 | it("has all functions", () => { 11 | for (const key of Object.keys(L)) { 12 | const elm = (L as any)[key]; 13 | // Check all non-anonymous functions 14 | if ( 15 | typeof elm === "function" && 16 | elm.name === key && 17 | exceptions.indexOf(elm.name) === -1 18 | ) { 19 | assert.isTrue(key in Lr); 20 | } 21 | } 22 | }); 23 | it("uses Ramdas equals", () => { 24 | const l = Lr.list( 25 | { foo: 1, bar: 2 }, 26 | { foo: 3, bar: 4 }, 27 | { foo: 5, bar: 6 } 28 | ); 29 | const idx = Lr.indexOf({ foo: 3, bar: 4 }, l); 30 | assert.strictEqual(idx, 1); 31 | }); 32 | */ 33 | it("list works with reduceBy", () => { 34 | type Student = { name: string; score: number }; 35 | const result = R.reduceBy( 36 | (acc, student) => acc.concat(student.name), 37 | [] as string[], 38 | (student: Student) => { 39 | const score = student.score; 40 | return score < 65 41 | ? "F" 42 | : score < 70 43 | ? "D" 44 | : score < 80 45 | ? "C" 46 | : score < 90 47 | ? "B" 48 | : "A"; 49 | }, 50 | L.list( 51 | { name: "Lucy", score: 92 }, 52 | { name: "Drew", score: 85 }, 53 | { name: "Bart", score: 62 } 54 | ) as any 55 | ); 56 | assert.deepEqual(result, { 57 | A: ["Lucy"], 58 | B: ["Drew"], 59 | F: ["Bart"] 60 | }); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /test/tree-shaking/README.md: -------------------------------------------------------------------------------- 1 | # Tree shaking size test 2 | 3 | This tests the bundle sizes achieved when including List in a 4 | production build with Webpack. The intention is to ensure that List is 5 | compatible with tree shaking. 6 | 7 | The following command generates the various bundles. 8 | 9 | ``` 10 | npm run build 11 | ``` 12 | 13 | And their sizes can be inspected with. 14 | 15 | ``` 16 | ls -lh dist 17 | ``` 18 | 19 | Which gives a result like the following. 20 | 21 | ``` 22 | 589B May 6 12:42 baseline.js 23 | 1.6K May 6 12:42 bundle1.js 24 | 3.6K May 6 12:42 bundle2.js 25 | 3.7K May 6 12:42 curried.js 26 | 21K May 6 12:42 methods.js 27 | ``` -------------------------------------------------------------------------------- /test/tree-shaking/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | List and Webpack tree shaking test 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /test/tree-shaking/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "list-tree-shaking-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "npm run build-baseline; npm run build-prod-1; npm run build-prod-2; npm run build-curried; npm run build-methods", 9 | "build-baseline": "webpack --mode production --entry ./src/baseline.js --output ./dist/baseline.js", 10 | "build-prod-1": "webpack --mode production --entry ./src/index1.js --output ./dist/bundle1.js", 11 | "build-prod-2": "webpack --mode production --entry ./src/index2.js --output ./dist/bundle2.js", 12 | "build-curried": "webpack --mode production --entry ./src/curried.js --output ./dist/curried.js", 13 | "build-methods": "webpack --mode production --entry ./src/methods.js --output ./dist/methods.js" 14 | }, 15 | "author": "", 16 | "license": "MIT", 17 | "devDependencies": { 18 | "webpack": "^4.7.0", 19 | "webpack-cli": "^5.0.1" 20 | }, 21 | "dependencies": { 22 | "list": "file:../.." 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/tree-shaking/src/baseline.js: -------------------------------------------------------------------------------- 1 | import * as L from "list"; 2 | 3 | console.log("no list"); 4 | -------------------------------------------------------------------------------- /test/tree-shaking/src/curried.js: -------------------------------------------------------------------------------- 1 | import * as L from "list/curried"; 2 | 3 | // Import two functions 4 | console.log(L.append(1)(L.list(0))); 5 | -------------------------------------------------------------------------------- /test/tree-shaking/src/index1.js: -------------------------------------------------------------------------------- 1 | import * as L from "list"; 2 | 3 | console.log(L.List); 4 | -------------------------------------------------------------------------------- /test/tree-shaking/src/index2.js: -------------------------------------------------------------------------------- 1 | import * as L from "list"; 2 | 3 | console.log(L.list, L.append); 4 | -------------------------------------------------------------------------------- /test/tree-shaking/src/methods.js: -------------------------------------------------------------------------------- 1 | import * as L from "list/methods"; 2 | 3 | // Use a function and a method 4 | console.log(L.list(0).append(1)); 5 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "noImplicitAny": true, 6 | "sourceMap": true 7 | }, 8 | "exclude": [ 9 | "node_modules" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/utils.ts: -------------------------------------------------------------------------------- 1 | import { Applicative } from "../src"; 2 | 3 | export abstract class Maybe implements Applicative { 4 | static "fantasy-land/of"(v: B): Maybe { 5 | return just(v); 6 | } 7 | abstract "fantasy-land/chain"(f: (a: A) => Maybe): Maybe; 8 | abstract "fantasy-land/map"(f: (a: A) => B): Maybe; 9 | abstract "fantasy-land/ap"(a: Applicative<(a: A) => B>): Applicative; 10 | } 11 | 12 | class Nothing extends Maybe { 13 | constructor() { 14 | super(); 15 | } 16 | "fantasy-land/chain"(_f: (v: A) => Maybe): Maybe { 17 | return nothing; 18 | } 19 | "fantasy-land/map"(_f: (a: A) => B): Maybe { 20 | return nothing; 21 | } 22 | "fantasy-land/ap"(_a: Maybe<(a: A) => B>): Maybe { 23 | return nothing; 24 | } 25 | } 26 | 27 | export class Just extends Maybe { 28 | constructor(readonly val: A) { 29 | super(); 30 | } 31 | "fantasy-land/chain"(f: (v: A) => Maybe): Maybe { 32 | return f(this.val); 33 | } 34 | "fantasy-land/map"(f: (a: A) => B): Maybe { 35 | return new Just(f(this.val)); 36 | } 37 | "fantasy-land/ap"(m: Maybe<(a: A) => B>): Maybe { 38 | return isJust(m) ? new Just(m.val(this.val)) : nothing; 39 | } 40 | } 41 | 42 | export function isJust(m: Maybe): m is Just { 43 | return m !== nothing; 44 | } 45 | 46 | export function just(v: V): Maybe { 47 | return new Just(v); 48 | } 49 | 50 | export const nothing: Maybe = new Nothing(); 51 | -------------------------------------------------------------------------------- /tsconfig-build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "commonjs", 6 | "declaration": true, 7 | "sourceMap": false, 8 | "outDir": "./dist", 9 | "rootDir": "./src", 10 | "lib": ["es5", "dom", "es2015.symbol", "es2015.iterable"], 11 | "removeComments": true 12 | }, 13 | "include": ["src/**/*.ts"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "declaration": true, 6 | "outDir": "./dist", 7 | "strict": true, 8 | "sourceMap": true, 9 | "noUnusedLocals": true, 10 | "strictPropertyInitialization": false, 11 | "noUnusedParameters": true 12 | }, 13 | "include": ["src/**/*.ts", "test/**/*.ts"], 14 | "exclude": ["node_modules"] 15 | } 16 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "warning", 3 | "rules": { 4 | "align": [true, "parameters", "statements"], 5 | "class-name": true, 6 | "comment-format": [true, "check-space"], 7 | "curly": true, 8 | "eofline": true, 9 | "forin": true, 10 | "indent": [true, "spaces"], 11 | "jsdoc-format": true, 12 | "label-position": true, 13 | "max-line-length": false, 14 | "member-access": false, 15 | "new-parens": true, 16 | "no-arg": true, 17 | "no-bitwise": false, 18 | "no-conditional-assignment": true, 19 | "no-consecutive-blank-lines": true, 20 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 21 | "no-construct": true, 22 | "no-debugger": true, 23 | "no-duplicate-variable": true, 24 | "no-eval": true, 25 | "no-inferrable-types": false, 26 | "no-internal-module": true, 27 | "no-null-keyword": false, 28 | "no-reference": true, 29 | "no-require-imports": true, 30 | "no-shadowed-variable": false, 31 | "no-string-literal": true, 32 | "no-trailing-whitespace": true, 33 | "no-unused-expression": true, 34 | "no-unused-variable": [true, { "ignore-pattern": "^_" }], 35 | "no-var-keyword": false, 36 | "no-var-requires": true, 37 | "one-line": [ 38 | true, 39 | "check-open-brace", 40 | "check-catch", 41 | "check-else", 42 | "check-finally", 43 | "check-whitespace" 44 | ], 45 | "one-variable-per-declaration": [true, "ignore-for-loop"], 46 | "quotemark": [true, "double", "avoid-escape"], 47 | "radix": true, 48 | "semicolon": [true, "always"], 49 | "switch-default": true, 50 | "trailing-comma": [ 51 | true, 52 | { 53 | "multiline": "never", 54 | "singleline": "never" 55 | } 56 | ], 57 | "triple-equals": [true], 58 | "typedef": [ 59 | true, 60 | "call-signature", 61 | "parameter", 62 | "property-declaration", 63 | "member-variable-declaration" 64 | ], 65 | "typedef-whitespace": [ 66 | true, 67 | { 68 | "call-signature": "nospace", 69 | "index-signature": "nospace", 70 | "parameter": "nospace", 71 | "property-declaration": "nospace", 72 | "variable-declaration": "nospace" 73 | }, 74 | { 75 | "call-signature": "space", 76 | "index-signature": "space", 77 | "parameter": "space", 78 | "property-declaration": "space", 79 | "variable-declaration": "space" 80 | } 81 | ], 82 | "use-isnan": true, 83 | "variable-name": [ 84 | true, 85 | "check-format", 86 | "allow-leading-underscore", 87 | "ban-keywords" 88 | ], 89 | "whitespace": [ 90 | true, 91 | "check-branch", 92 | "check-decl", 93 | "check-operator", 94 | "check-separator", 95 | "check-type" 96 | ] 97 | } 98 | } 99 | --------------------------------------------------------------------------------