├── .gitignore ├── LICENSE ├── README.md ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | sketchpad.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Frank Stokes 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lazy-infinite 2 | A Fantasy Land Compliant, Lazily Evaluated Infinite List Data Structure. 3 | 4 | `lazy-infinite` uses generators to define a potentially infinite data structure, and allows you to describe transforming the elements in that structure without evaluating it. 5 | 6 | ## Installation 7 | 8 | ```bash 9 | npm i lazy-infinite 10 | ``` 11 | 12 | ## Usage 13 | 14 | ```javascript 15 | const Infinite = require('lazy-infinite'); 16 | 17 | // Create a representation of an infinite structure 18 | const naturalNumbers = Infinite.generator(function*() { 19 | let x = 0; 20 | while (true) yield x++; 21 | }); 22 | 23 | // Transform to yield new infinite structures 24 | const primes = naturalNumbers 25 | .filterDependent((x, list) => { 26 | if (!(x > 1 && (x % 2 === 1 || x === 2))) return false; 27 | for (let i = 0; i < list.length; i++) { 28 | const y = list[i]; 29 | if (y > x / 2) break; 30 | if (x % y === 0) return false; 31 | } 32 | return true; 33 | }); 34 | 35 | // Concretely evaluate only what is required 36 | const first1000Primes = primes.take(1000); 37 | // -> [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, ... 38 | ``` 39 | 40 | ## API 41 | 42 | ### take 43 | 44 | `take :: Infinite a ~> Integer -> [a]` 45 | 46 | `take` receives an argument *n*, and returns a concrete list with *n* elements. 47 | 48 | **Example** 49 | ```javascript 50 | naturalNumbers.take(5); 51 | // -> [ 0, 1, 2, 3, 4 ] 52 | ``` 53 | 54 | ### takeContinuous 55 | 56 | `takeContinuous :: Infinite a ~> Integer -> [[a], Infinite a]` 57 | 58 | `takeContinuous` is just like take, but it returns an array of two items: the *concrete list* and a new *Infinite* that produces elements where the previous one left off. 59 | 60 | **Example** 61 | ```javascript 62 | let [concrete, nextPrimes] = primes.takeContinuous(5); 63 | console.log(concrete); 64 | // -> [ 2, 3, 5, 7, 11 ] 65 | 66 | [concrete, nextPrimes] = nextPrimes.takeContinuous(5); 67 | console.log(concrete); 68 | // -> [ 13, 17, 19, 23, 29 ] 69 | ``` 70 | 71 | ### nth 72 | 73 | `nth :: Infinite a ~> Integer -> a` 74 | 75 | `nth` takes an argument *n* and returns the *nth* element of the concretely evaluated list. 76 | 77 | **Example** 78 | ```javascript 79 | primes.nth(5); 80 | // -> 11 81 | ``` 82 | 83 | ### drop 84 | 85 | `drop :: Infinite a ~> Integer -> Infinite a` 86 | 87 | `drop` takes a number *n* and returns a new `Infinite` with the first *n* elements removed. 88 | 89 | **Example** 90 | ```javascript 91 | naturalNumbers 92 | .drop(5) 93 | .take(5); 94 | // -> [ 5, 6, 7, 8, 9 ] 95 | ``` 96 | 97 | ### map 98 | 99 | `map :: Infinite a ~> (a -> b) -> Infinite b` 100 | 101 | `map` takes a function and applies it to every element in the list. 102 | 103 | **Example** 104 | ```javascript 105 | naturalNumbers 106 | .map(x => -x) 107 | .take(5); 108 | // -> [ -0, -1, -2, -3, -4 ] 109 | ``` 110 | 111 | ### flatMap 112 | 113 | `flatMap :: Infinite a ~> (a -> [b]) -> Infinite b` 114 | 115 | `flatMap` runs a function on every element of the list and concatenates the results. 116 | 117 | **Example** 118 | ```javascript 119 | naturalNumbers 120 | .flatMap(x => naturalNumbers.take(x)) 121 | .take(10); 122 | // -> [ 0, 0, 1, 0, 1, 2, 0, 1, 2, 3 ] 123 | ``` 124 | 125 | ### mapIndexed 126 | 127 | `mapIndexed :: Infinite a ~> (a -> Int -> b) -> Infinite b` 128 | 129 | `mapIndexed` is just like [map](#map), except the function it is passed receives an index as it's second argument. 130 | 131 | **Example** 132 | ```javascript 133 | primes 134 | .mapIndexed((x, i) => `Prime #${i}: ${x}`) 135 | .take(5); 136 | // -> [ 137 | // 'Prime #0: 2', 138 | // 'Prime #1: 3', 139 | // 'Prime #2: 5', 140 | // 'Prime #3: 7', 141 | // 'Prime #4: 9' 142 | // ] 143 | ``` 144 | 145 | ### filter 146 | 147 | `filter :: Infinite a ~> (a -> Bool) -> Infinite a` 148 | 149 | `filter` takes a function, and drops any elements from the infinite list that return `false` when this function is applied. 150 | 151 | **Example** 152 | ```javascript 153 | naturalNumbers 154 | .filter(x => x % 2 === 0) 155 | .take(5); 156 | // -> [ 0, 2, 4, 6, 8 ] 157 | ``` 158 | 159 | ### filterIndexed 160 | 161 | `filterIndexed :: Infinite a ~> (a -> Int -> b) -> Infinite b` 162 | 163 | `filterIndexed` is just like [filter](#filter), except the function it is passed receives an index as it's second argument. 164 | 165 | **Example** 166 | ```javascript 167 | primes 168 | .filterIndexed((x, i) => i % 2 === 0) 169 | .take(5) 170 | // -> [ 2, 5, 9, 13, 19 ] 171 | ``` 172 | 173 | ### filterDependent 174 | 175 | `filterDependent :: Infinite a ~> (a -> [a] -> Bool) -> Infinite a` 176 | 177 | `filterDependent` is just like [filter](#filter), except that the function it is passed receives the *list of items before it* as the second argument. 178 | 179 | **Example** 180 | ```javascript 181 | naturalNumbers 182 | .filterDependent((x, list) => x !== list.length) 183 | .take(5); 184 | // -> [ 1, 2, 3, 4, 5 ] 185 | ``` 186 | 187 | ### zip 188 | 189 | `zip :: Infinite a ~> Infinite b -> Infinite [a, b]` 190 | 191 | `zip` takes another `Infinite`, and returns a new Infinite whose elements are an array with the original value and a corresponding value from the other infinte. 192 | 193 | This can be used to create custom *indexing*. 194 | 195 | **Example** 196 | ```javascript 197 | const fibonacci = Infinite.generator(function* () { 198 | let a = 0; 199 | let b = 1; 200 | while (true) { 201 | yield b; 202 | const tmp = b; 203 | b += a; 204 | a = tmp; 205 | } 206 | }); 207 | 208 | primes 209 | .zip(fibonacci) 210 | .take(5); 211 | // -> [ [2, 1], [3, 1], [5, 2], [7, 3], [9, 5] ] 212 | ``` 213 | 214 | ### intersperse 215 | 216 | `intersperse :: Infinite a ~> Infinite b -> Infinite a|b` 217 | 218 | `intersperse` takes another `Infinite`, and returns a new Infinite whose elements alternate between the first and second Infnites. 219 | 220 | **Example** 221 | ```javascript 222 | const fibonacci = Infinite.generator(function* () { 223 | let a = 0; 224 | let b = 1; 225 | while (true) { 226 | yield b; 227 | const tmp = b; 228 | b += a; 229 | a = tmp; 230 | } 231 | }); 232 | 233 | primes 234 | .intersperse(fibonacci) 235 | .take(5); 236 | // -> [ 2, 1, 3, 1, 5 ] 237 | ``` 238 | 239 | ### toGenerator 240 | 241 | `toGenerator :: Infinite a ~> () -> Generator a` 242 | 243 | `toGenerator` returns a generator function that represents this `Infinite`s computation. 244 | 245 | **Example** 246 | ```javascript 247 | const primeGeneratorFn = primes.toGenerator(); 248 | 249 | const primeGen = primeGeneratorFn(); 250 | primeGen.next(); 251 | // -> { value: 2, done: false } 252 | primeGen.next(); 253 | // -> { value: 3, done: false } 254 | primeGen.next(); 255 | // -> { value: 5, done: false } 256 | ``` 257 | 258 | ### Infinite.generator 259 | 260 | `Infinite.generator :: Generator a -> Infinite a` 261 | 262 | `Infinite.generator` takes a potentially infinite generator function and returns an `Infinite` list. 263 | 264 | **Example** 265 | ```javascript 266 | const odds = Infinite.generator(function*() { 267 | let x = 1; 268 | while (true) { 269 | yield x; 270 | x += 2; 271 | } 272 | }); 273 | ``` 274 | 275 | ### Infinite.toGenerator 276 | 277 | `Infinite.toGenerator :: Infinite a -> Generator a` 278 | 279 | `Infinite.toGenerator` takes an `Infinite` list and returns a generator function that represents it's computation. 280 | 281 | **Example** 282 | ```javascript 283 | const primeGeneratorFn = Infinite.toGenerator(primes); 284 | 285 | const primeGen = primeGeneratorFn(); 286 | primeGen.next(); 287 | // -> { value: 2, done: false } 288 | primeGen.next(); 289 | // -> { value: 3, done: false } 290 | primeGen.next(); 291 | // -> { value: 5, done: false } 292 | ``` 293 | 294 | ### Infinite.from 295 | 296 | `Infinite.from :: (a -> a) -> a -> Infinite a` 297 | 298 | `Infinite.from` takes *next value* function and a *start* value, and returns an `Infinite` with an automatically constructed iterator. 299 | 300 | **Example** 301 | ```javascript 302 | const odds = Infinite.from(x => x + 2, 1); 303 | ``` 304 | 305 | ### Infinite.fromIterable 306 | 307 | `Infinite.fromIterable :: Iterable a -> Infinite a` 308 | 309 | `Infinite.fromIterable` takes anything conforming to the `Iterable` interface and returns an `Infinite`. 310 | 311 | **Example** 312 | ```javascript 313 | Infinite.fromIterable([1,2,3,4,5,6,7]).take(5) 314 | // -> [ 1, 2, 3, 4, 5 ] 315 | ``` 316 | 317 | ## Fantasy Land 318 | 319 | Supports\*: `Functor` and `Filterable`. 320 | 321 | *\*Since a truly valid `of` static method cannot be written for `Infinite` it does not properly conform to either interface* 322 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // data Infinite a 2 | function Infinite(gen) { 3 | this.gen = gen; 4 | }; 5 | 6 | // indexingSet :: Infinite Integer 7 | const indexingSet = new Infinite(function*(){ 8 | let x = 0; 9 | for (;;) yield x++; 10 | }); 11 | 12 | // drop :: Infinite a ~> Integer -> Infinite a 13 | Infinite.prototype.drop = function (n) { 14 | if (typeof n !== 'number' || !(n < Infinity)) { 15 | throw new Error(`n must be a number less than Infinity`); 16 | } 17 | 18 | const that = this; 19 | return Infinite.generator(function* () { 20 | const i = that.gen(); 21 | let dropped = 0; 22 | while (dropped++ < n) i.next(); 23 | 24 | while (true) { 25 | const v = i.next(); 26 | if (v.done) break; 27 | yield v.value; 28 | } 29 | }); 30 | }; 31 | 32 | // intersperse :: Infinite a ~> Infinite b -> Infinite a|b 33 | Infinite.prototype.intersperse = function (inf) { 34 | if (!(inf instanceof Infinite)) { 35 | throw new Error(`Argument to intersperse must be another Infinite`); 36 | } 37 | 38 | const that = this; 39 | return Infinite.generator(function* () { 40 | const i = that.gen(); 41 | const i2 = inf.gen(); 42 | let useFirst = true; 43 | 44 | while (true) { 45 | const v = useFirst ? i.next() : i2.next(); 46 | if (v.done) break; 47 | yield v.value; 48 | useFirst = !useFirst; 49 | } 50 | }); 51 | }; 52 | 53 | // zip :: Infinite a ~> Infinite b -> Infinite [a, b] 54 | Infinite.prototype.zip = function (inf) { 55 | if (!(inf instanceof Infinite)) { 56 | throw new Error(`Argument to zip must be another Infinite`); 57 | } 58 | 59 | const that = this; 60 | return Infinite.generator(function* () { 61 | const i1 = that.gen(); 62 | const i2 = inf.gen(); 63 | while (true) { 64 | const v1 = i1.next(); 65 | const v2 = i2.next(); 66 | if (v1.done || v2.done) break; 67 | yield [v1.value, v2.value]; 68 | } 69 | }); 70 | }; 71 | 72 | // map :: Infinite a ~> (a -> b) -> Infinite b 73 | Infinite.prototype['fantasy-land/map'] = Infinite.prototype.map = function (fn) { 74 | const that = this; 75 | return Infinite.generator(function* () { 76 | const i = that.gen(); 77 | while (true) { 78 | const v = i.next(); 79 | if (v.done) break; 80 | yield fn(v.value); 81 | } 82 | }); 83 | }; 84 | 85 | // flatMap :: Infinite a ~> (a -> [b]) -> Infinite b 86 | Infinite.prototype.flatMap = function (fn) { 87 | const that = this; 88 | return Infinite.generator(function* () { 89 | const i = that.gen(); 90 | while (true) { 91 | const v = i.next(); 92 | if (v.done) break; 93 | const vs = fn(v.value); 94 | for (let j = 0; j < vs.length; j++) { 95 | yield vs[j]; 96 | } 97 | } 98 | }); 99 | }; 100 | 101 | // mapIndexed :: Infinite a ~> (a -> Integer -> b) -> Infinite b 102 | Infinite.prototype.mapIndexed = function (fn) { 103 | return this.zip(indexingSet) 104 | .map(([x, i]) => fn(x, i)); 105 | }; 106 | 107 | // filter :: Infinite a ~> (a -> Bool) -> Infinite b 108 | Infinite.prototype['fantasy-land/filter'] = Infinite.prototype.filter = function (fn) { 109 | const that = this; 110 | return Infinite.generator(function* () { 111 | const i = that.gen(); 112 | while (true) { 113 | const v = i.next(); 114 | if (v.done) break; 115 | if (fn(v.value)) { 116 | yield v.value; 117 | } 118 | } 119 | }); 120 | }; 121 | 122 | // filterIndexed :: Infinite a ~> (a -> Integer -> Bool) -> Infinite a 123 | Infinite.prototype.filterIndexed = function (fn) { 124 | return this.zip(indexingSet) 125 | .filter(([x, i]) => fn(x, i)) 126 | .map(([x]) => x); 127 | }; 128 | 129 | // filterDependent :: Infinite a ~> (a -> [a] -> Bool) -> Infinite b 130 | Infinite.prototype.filterDependent = function (fn) { 131 | const that = this; 132 | return Infinite.generator(function* () { 133 | const i = that.gen(); 134 | const prev = []; 135 | 136 | while (true) { 137 | const v = i.next(); 138 | if (v.done) break; 139 | if (fn(v.value, prev)) { 140 | prev.push(v.value); 141 | yield v.value; 142 | } 143 | } 144 | }); 145 | }; 146 | 147 | // takeContinuous :: Infinite a ~> Integer -> [[a], Infinite a] 148 | Infinite.prototype.takeContinuous = function (n) { 149 | const i = this.gen(); 150 | const concrete = new Array(n); 151 | let index = 0; 152 | while (index < n) { 153 | const v = i.next(); 154 | if (v.done) return concrete.slice(0, index); 155 | concrete[index++] = v.value; 156 | } 157 | return [concrete, Infinite.generator(() => i)]; 158 | }; 159 | 160 | // take :: Infinite a ~> Integer -> [a] 161 | Infinite.prototype.take = function (n) { 162 | return this.takeContinuous(n)[0]; 163 | }; 164 | 165 | // nth :: Infinite a ~> Integer -> a 166 | Infinite.prototype.nth = function (n) { 167 | return this.take(n)[n-1]; 168 | }; 169 | 170 | // toGenerator :: Infinite a ~> () -> Generator a 171 | Infinite.prototype.toGenerator = function () { 172 | return this.gen; 173 | }; 174 | 175 | // generator :: Generator a -> Infinite a 176 | Infinite.generator = gen => new Infinite(gen); 177 | 178 | // toGenerator :: Infinite a -> Generator a 179 | Infinite.toGenerator = inf => inf.gen; 180 | 181 | // from :: (a -> a) -> a -> Infinite a 182 | Infinite.from = (fn, start) => Infinite.generator(function* () { 183 | let x = start; 184 | while (true) { 185 | yield x; 186 | x = fn(x); 187 | } 188 | }); 189 | 190 | // fromIterable :: Iterable a -> Infinite a 191 | Infinite.fromIterable = it => { 192 | if (typeof it[Symbol.iterator] !== 'function') { 193 | throw new Error(`Cannot create Infinite from non-iterable`); 194 | } 195 | return Infinite.generator(() => it[Symbol.iterator]()); 196 | }; 197 | 198 | module.exports = Infinite; 199 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lazy-infinite", 3 | "version": "0.4.0", 4 | "description": "A Fantasy Land Compliant, Lazily Evaluated Infinite List Data Structure", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Francis Stokes", 10 | "license": "ISC" 11 | } 12 | --------------------------------------------------------------------------------