Skip to content

Instantly share code, notes, and snippets.

@briancavalier
Last active November 24, 2021 20:10
Show Gist options
  • Save briancavalier/abc713b6806f37c687a6 to your computer and use it in GitHub Desktop.
Save briancavalier/abc713b6806f37c687a6 to your computer and use it in GitHub Desktop.
Infinite "lists" in es6 using generators and yield
// Requires traceur
import { iterate, take, map, foldl } from './list-out-of-yield';
// Sum of squares of integers 1..100
var values = take(100, iterate(x => x + 1, 1));
var result = foldl((x, y) => x + y, 0, map(x => x * x, values));
console.log(result);
// empty list
export function* empty() {}
// list containing all arguments
export function* list(...xs) {
yield* xs;
}
// list with head x, tail list
export function* cons(x, list) {
yield x, yield* list;
}
// concat lists a and b
export function* concat(a, b) {
yield* a, yield* b;
}
// infinite list of x, f(x), f(f(x)), etc.
export function* iterate(f, x) {
yield* unfold(x => [x, f(x)], x);
}
// infinite list by unfolding f, starting with x
export function* unfold(f, x) {
// No tail recursion yet, will blow the stack, oh well.
// See https://github.com/google/traceur-compiler/issues/218
var [a, b] = f(x);
yield a, yield* unfold(f, b);
}
// infinite list of xs
export function* repeat(x) {
yield x, yield* repeat(x);
}
// fold list from the left
export function foldl(f, z, list) {
// This "loop" is a hack just to get the head of the list
// Is there a better way without resorting to using iterator.next()?
for(var x of list) {
return foldl(f, f(z, x), list);
}
return z;
}
// fold list from the right
export function foldr(f, z, list) {
// This "loop" is a hack just to get the head of the list
// Is there a better way without resorting to using iterator.next()?
for(var x of list) {
return f(x, foldr(f, z, list));
}
return z;
}
export function* scanl(f, z, list) {
for(var x of list) { // loop :(
yield z = f(z, x);
}
return z;
}
// list containing at most first n elements
export function* take(n, list) {
yield* takeWhile(() => --n >= 0, list);
}
export function* takeWhile(f, list) {
for(var x of list) { // loop :(
if(!f(x)) {
break;
}
yield x;
}
}
export function* drop(n, list) {
yield* dropWhile(() => --n > 0, list);
}
export function* dropWhile(f, list) {
for(var x of list) { // loop :(
if(!f(x)) {
break;
}
}
yield* list;
}
// list of items transformed by applying f(x)
export function* map(f, list) {
for(var x of list) { // loop :(
yield f(x);
}
}
// apply all the fs to all the xs
export function* ap(fs, xs) {
// WARNING: Doesn't work if xs is a generator iterable, because
// generator iterables are not restartable
yield* chain(function*(f) {
yield* map(f, xs);
}, fs);
}
// aka flatMap/bind
export function* chain(f, list) {
for(var x of list) { // loop :(
yield* f(x);
}
}
// list containing only items where f(x) is truthy
export function* filter(f, list) {
for(var x of list) { // loop :(
if(f(x)) {
yield x;
}
}
}
@rauschma
Copy link

rauschma commented May 6, 2014

Nice! What’s the reason for using the comma operator instead of a semicolon (e.g. line 11 and line 16)?

@briancavalier
Copy link
Author

@rauschma thanks! the comma was mostly just playing around to see in what interesting ways I could lay out the code. kinda liked the readability of the "yield head, yield tail" style, like a prose list, e.g. break, milk :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment