lazy-seq
Version:
Lazy sequences
456 lines (381 loc) • 10.5 kB
JavaScript
/**
# lazy-seq
> Lazy sequences
[](http://travis-ci.org/phadej/lazy-seq)
[](http://badge.fury.io/js/lazy-seq)
[](https://david-dm.org/phadej/lazy-seq)
[](https://david-dm.org/phadej/lazy-seq#info=devDependencies)
[](https://codeclimate.com/github/phadej/lazy-seq)
## Lazy?
The list structure could be defined as
```hs
data Seq a = Nil | Cons a (Seq a)
```
The `Cons` constuctor takes two arguments, so there are four different laziness variants:
```hs
Cons (Strict a) (Strict (Seq a)) -- 1. fully strict
Cons (Lazy a) (Strict (Seq a)) -- 2. lazy values
Cons (Strict a) (Lazy (Seq a)) -- 3. lazy structure
Cons (Lazy a) (Lazy (Seq a)) -- 4. fully lazy
```
This module implements the third variant: lazy structure, but strict values.
## Example
```js
var ones = lazyseq.cons(1, function () { return ones; });
console.log(ones === ones.tail()); // true!
```
## Why?
This package is originally made to optimise shrink operations in [jsverify](http://jsverify.github.io/), a property-based testing library.
## API
*/
"use strict";
var assert = require("assert");
/**
- *nil : Seq a* — Empty sequence.
- *cons : (head : a, tail : Array a | Seq a | () → Array a | () → Seq a) → Seq a* : Cons a value to the front of a sequence (list or thunk).
*/
var nil = {};
/**
- *.isNil : Boolean* — Constant time check, whether the sequence is empty.
*/
nil.isNil = true;
/**
- *.toString : () → String* — String representation. Doesn't force the tail.
*/
nil.toString = function () {
return "nil";
};
/**
- *.length : () → Nat* — Return the length of the sequene. Forces the structure.
*/
nil.length = function () {
return 0;
};
/**
- *.toArray : () → Array a* — Convert the sequence to JavaScript array.
*/
nil.toArray = function nilToArray() {
return [];
};
/**
- *.fold : (z : b, f : (a, () → b) → b) → b* — Fold from right.
```hs
fold nil x f = x
fold (cons h t) x f = f x (fold t x f)
```
*/
nil.fold = function nilFold(x /*, f */) {
return x;
};
/**
- *.head : () → a* — Extract the first element of a sequence, which must be non-empty.
*/
nil.head = function nilHead() {
throw new Error("nil.head");
};
/**
- *.tail : () → Seq a* — Return the tail of the sequence.
```hs
tail nil = nil
tail (cons h t) = t
```
*/
nil.tail = function nilTail() {
return nil;
};
/**
- *.nth : (n : Nat) → a* — Return nth value of the sequence.
*/
nil.nth = function nilNth(n) {
assert(typeof n === "number");
throw new Error("Can't get " + n + "th value of the nil");
};
/**
- *.take : (n : Nat) → Seq a* — Take `n` first elements of the sequence.
*/
nil.take = function (n) {
assert(typeof n === "number");
return nil;
};
/**
- *.drop : (n : Nat) → Seq a* — Drop `n` first elements of the sequence.
*/
nil.drop = function (n) {
assert(typeof n === "number");
return nil;
};
/**
- *.map : (f : a → b) : Seq b* — The sequence obtained by applying `f` to each element of the original sequence.
*/
nil.map = function (f) {
assert(typeof f === "function");
return nil;
};
/**
- *.append : (ys : Seq a | Array a) : Seq a* — Append `ys` sequence.
*/
nil.append = function (seq) {
if (typeof seq === "function") {
seq = seq();
}
if (Array.isArray(seq)) {
/* eslint-disable no-use-before-define */
return fromArray(seq);
/* eslint-enable no-use-before-define */
} else {
return seq;
}
};
/**
- *.filter : (p : a -> bool) : Seq a* — filter using `p` predicate.
*/
nil.filter = function () {
return nil;
};
/**
- *.every : (p = identity: a -> b) : b | true — return first falsy value in the sequence, true otherwise. *N.B.* behaves slightly differently from `Array::every`.
*/
nil.every = function () {
return true;
};
/**
- *.some : (p = identity: a -> b) : b | false — return first truthy value in the sequence, false otherwise. *N.B.* behaves slightly differently from `Array::some`.
*/
nil.some = function () {
return false;
};
/**
- *.contains : (x : a) : bool — Returns `true` if `x` is in the sequence.
*/
nil.contains = function () {
return false;
};
/**
- *.containsNot : (x : a) : bool — Returns `true` if `x` is not in the sequence.
*/
nil.containsNot = function () {
return true;
};
// Default cons values are with strict semantics
function Cons(head, tail) {
this.headValue = head;
this.tailValue = tail;
}
Cons.prototype.isNil = false;
Cons.prototype.toString = function () {
return "Cons(" + this.headValue + ", " + this.tailValue + ")";
};
Cons.prototype.length = function () {
return 1 + this.tail().length();
};
Cons.prototype.toArray = function () {
var ptr = this;
var acc = [];
while (ptr !== nil) {
acc.push(ptr.headValue);
ptr = ptr.tail();
}
return acc;
};
Cons.prototype.fold = function consFold(x, f) {
var self = this;
return f(this.headValue, function () {
return self.tail().fold(x, f);
});
};
Cons.prototype.head = function consHead() {
return this.headValue;
};
Cons.prototype.tail = function consTail() {
return this.tailValue;
};
// But when cons is created, it's overloaded with lazy ones
// Force tail to whnf.
function lazyConsForce() {
/* jshint validthis:true */
var val = this.tailFn();
/* eslint-disable no-use-before-define */
this.tailValue = Array.isArray(val) ? fromArray(val) : val;
/* eslint-enable no-use-before-define */
delete this.tail;
delete this.force;
return this;
}
function lazyConsTail() {
/* jshint validthis:true */
this.force();
return this.tailValue;
}
function delay(head, tail) {
assert(typeof tail === "function");
head.tailFn = tail;
head.tail = lazyConsTail;
head.force = lazyConsForce;
return head;
}
function cons(head, tail) {
if (typeof tail === "function") {
return delay(new Cons(head), tail);
} else if (Array.isArray(tail)) {
return delay(cons(head), function () {
/* eslint-disable no-use-before-define */
return fromArray(tail);
/* eslint-enable no-use-before-define */
});
} else {
return new Cons(head, tail);
}
}
// Rest of the functions. They might use cons
Cons.prototype.nth = function consNth(n) {
assert(typeof n === "number");
return n === 0 ? this.headValue : this.tail().nth(n - 1);
};
Cons.prototype.take = function consTake(n) {
assert(typeof n === "number");
var that = this;
return n === 0 ? nil : cons(this.headValue, function () {
return that.tail().take(n - 1);
});
};
Cons.prototype.drop = function consDrop(n) {
assert(typeof n === "number");
return n === 0 ? this : this.tail().drop(n - 1);
};
Cons.prototype.map = function consMap(f) {
assert(typeof f === "function");
var that = this;
return cons(f(that.headValue), function () {
return that.tail().map(f);
});
};
Cons.prototype.append = function consAppend(seq) {
// Short circuit decidable: (++ []) ≡ id
if (seq === nil || (Array.isArray(seq) && seq.length === 0)) {
return this;
}
var that = this;
return cons(that.headValue, function () {
return that.tail().append(seq);
});
};
Cons.prototype.filter = function consFilter(p) {
assert(typeof p === "function");
var that = this;
if (p(that.headValue)) {
return cons(that.headValue, function () {
return that.tail().filter(p);
});
} else {
return that.tail().filter(p);
}
};
Cons.prototype.every = function consEvery(p) {
p = p || function (x) { return x; };
assert(typeof p === "function");
var that = this;
var pHead = p(that.headValue);
if (!pHead) {
return pHead;
} else {
return that.tail().every(p);
}
};
Cons.prototype.some = function consSome(p) {
p = p || function (x) { return x; };
assert(typeof p === "function");
var that = this;
var pHead = p(that.headValue);
if (pHead) {
return pHead;
} else {
return that.tail().some(p);
}
};
Cons.prototype.contains = function consContains(x) {
var that = this;
if (x === that.headValue) {
return true;
} else {
return that.tail().contains(x);
}
};
Cons.prototype.containsNot = function consContainsNot(x) {
var that = this;
if (x === that.headValue) {
return false;
} else {
return that.tail().containsNot(x);
}
};
// Constructors
/**
- *fromArray: (arr : Array a) → Seq a* — Convert a JavaScript array into lazy sequence.
*/
function fromArrayIter(arr, n) {
if (n < arr.length) {
return cons(arr[n], function () {
return fromArrayIter(arr, n + 1);
});
} else {
return nil;
}
}
function fromArray(arr) {
assert(Array.isArray(arr));
return fromArrayIter(arr, 0);
}
/**
- *singleton: (x : a) → Seq a* — Create a singleton sequence.
*/
function singleton(x) {
return fromArray([x]);
}
/**
- *append : (xs... : Array a | Seq a | () → Array a | () → Seq a) → Seq a* : Append one sequence-like to another.
*/
function append() {
var acc = nil;
for (var i = 0; i < arguments.length; i++) {
acc = acc.append(arguments[i]);
}
return acc;
}
/**
- *iterate : (x : a, f : a → a) → Seq a* — Create an infinite sequence of repeated applications of `f` to `x`: *x, f(x), f(f(x))…*.
*/
function iterate(x, f) {
return cons(x, function () {
return iterate(f(x), f);
});
}
/**
- *fold : (seq : Seq a | Array a, z : b, f : (a, () → b) → b) : b* — polymorphic version of fold. Works with arrays too.
*/
function listFold(list, z, f, n) {
if (n < list.length) {
return f(list[n], function () {
return listFold(list, z, f, n + 1);
});
} else {
return z;
}
}
function fold(list, z, f) {
if (Array.isArray(list)) {
return listFold(list, z, f, 0);
} else {
return list.fold(z, f);
}
}
module.exports = {
nil: nil,
cons: cons,
append: append,
fromArray: fromArray,
singleton: singleton,
iterate: iterate,
fold: fold,
};
/// plain CHANGELOG.md
/// plain CONTRIBUTING.md