Created
May 20, 2021 21:09
-
-
Save mrozbarry/df9eef5d8749808dc34bb3dcfdf72159 to your computer and use it in GitHub Desktop.
A friend of mine was really _taken_ by Haskell's `take <count> 1..5` functionality, and while there were naysayers, I said it could be duplicated in javascript with generators. Here it is, in all of it's glory
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function *makeNumbers(numberStep = 1, startAt = 0) { | |
for(number = startAt; ; number += numberStep) yield number; | |
} | |
export class Range { | |
constructor(start = 0, end = 0, step = 1) { | |
this._from = start; | |
this._to = end; | |
this._step = step; | |
} | |
from(number) { | |
return new Range(number, this._to, this._step); | |
} | |
to(number) { | |
return new Range(this._from, number, this._step); | |
} | |
step(number) { | |
return new Range(this._from, this._to, number); | |
} | |
toArray() { | |
const iterator = makeNumbers(this._step, this._from); | |
const maxIterations = Math.abs(this._to - this._from) + 1; | |
const collect = (iteration = 0) => { | |
if (iteration >= maxIterations) return []; | |
const result = iterator.next(); | |
if (result.done) return []; | |
return [ | |
result.value, | |
...collect(iteration + 1), | |
]; | |
} | |
return collect(); | |
} | |
slice(start, end) { | |
const step = end > start ? Math.abs(this._step) : Math.abs(this._step) * -1; | |
const _end = end >= 0 ? end : start + end; | |
return this.from(start).to(_end).step(step).toArray(); | |
} | |
take(count) { | |
const iterator = makeNumbers(this._step, this._from); | |
return Array.from( | |
{ length: count }, | |
() => { | |
const result = iterator.next(); | |
if (result.done) return null; | |
return result.value; | |
}, | |
).filter(v => v !== null) | |
} | |
} | |
export const range = (words, min, max) => { | |
const [def] = words.filter(w => w); | |
return (new Range()) | |
.from(min) | |
.to(def === '...' ? max : max - 1) | |
.toArray(); | |
}; | |
export const take = (count, pattern) => { | |
const [first, second] = pattern; | |
const diff = Math.abs(second - first); | |
const direction = Math.sign(second - first); | |
return (new Range()) | |
.step(diff * direction) | |
.from(first) | |
.take(count); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Range, range, take } from './range.js'; | |
// The haskell clone | |
console.log( | |
take(5, [3, 7]) | |
), | |
// Output: [3, 7, 11, 15, 19] | |
// And other interesting things | |
// Compact | |
console.log( | |
(new Range(0, 10, 2)).toArray() | |
); | |
// Output: [0, 2, 4, 6, 8, 10] | |
// Verbose | |
console.log( | |
(new Range()) | |
.from(0) | |
.to(10) | |
.step(2) | |
.toArray() | |
); | |
// Output: [0, 2, 4, 6, 8, 10] | |
// Fancy-pants, but no steps | |
console.log( | |
range`${0}...${10}` | |
); | |
// Output: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment