This repository has been archived by the owner on Oct 24, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactored
seq
again; also added the lim
function
The signature of `seq` is now much less smelly. The `length` field is still around, for those lazy sequences that *do* know their length up front (range, for instance.) The `lim` function was added as an abstraction to the common case of having functions that should only be callable a set number of times. This makes it very easy (and safe!) to implement bounded sequences, by simply limiting the number of times the `next` function can be called. This is also used within `seq` to limit calls to `next` in those cases that a length is specified and the collection is lazy. A `seq` can be lazy, not report a length, and *still* be bounded; as is shown by `take` which doesn't report its length even though it possibly could. This is because while it's true that `take` has an upper bound of `n`, it may actually be less than that in case the underlying sequence has fewer items. I figured the principle of least surprise dictates in this case that `take` should simply have an undefined length.
- Loading branch information
Showing
7 changed files
with
133 additions
and
147 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
module.exports = lim | ||
This comment has been minimized.
Sorry, something went wrong. |
||
|
||
function lim(n, fn) { | ||
if (!isNum(n) || n < 0) throw new TypeError('Limit must be a positive integer.') | ||
if (!isFn(fn)) throw new TypeError('Second argument must be a function.') | ||
|
||
var c = 0 | ||
|
||
return function ltd() { | ||
return c < n? fn.apply(this, [c++].concat(arguments)) : nil | ||
} | ||
} | ||
|
||
const isNum = require('./isNum') | ||
, slice = require('./slice') | ||
, isFn = require('./isFn') | ||
, nil = require('./nil') |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,110 +1,84 @@ | ||
module.exports = seq | ||
|
||
function seq(first, next, size, desc) { | ||
const argc = count(arguments) | ||
function seq(x, length) { | ||
if (isNil(x)) return nil | ||
if (seq.isPrototypeOf(x)) return x | ||
|
||
if (argc > 1) { | ||
return make(first, next, size, desc) | ||
} else if (seq.isPrototypeOf(first)) { | ||
return first | ||
} else if (isNil(first)) { | ||
return nil | ||
} | ||
const cons = from[type(x)] | ||
|
||
const s = seqer[type(first)] | ||
|
||
if (!s) { | ||
throw new TypeError("Don't know how to create a sequence from " + type(first)) | ||
if (!cons) { | ||
throw new TypeError("Don't know how to create a sequence from " + type(x)) | ||
} | ||
|
||
return count(first)? s(first) : nil | ||
if (cons === lazy && isNum(length) && length >= 0) { | ||
return lazy(lim(length, x), length) | ||
} else { | ||
return cons(x) | ||
} | ||
} | ||
|
||
const count = require('./count') | ||
, isNum = require('./isNum') | ||
, isNil = require('./isNil') | ||
, isStr = require('./isStr') | ||
, once = require('./once') | ||
, type = require('./type') | ||
, isFn = require('./isFn') | ||
, keys = Object.keys | ||
, prop = Object.defineProperty | ||
, nil = require('./nil') | ||
, src = require('./src') | ||
, lt = require('./lt') | ||
|
||
const seqer = | ||
{ 'array' : fromArray | ||
, 'string' : fromString | ||
, 'object' : fromObject | ||
const constantly = require('./constantly') | ||
, partial = require('./partial') | ||
, isNil = require('./isNil') | ||
, isNum = require('./isNum') | ||
, once = require('./once') | ||
, type = require('./type') | ||
, keys = Object.keys | ||
, lim = require('./lim') | ||
, nil = require('./nil') | ||
, lt = require('./lt') | ||
|
||
const from = | ||
{ 'array' : vec | ||
, 'string' : str | ||
, 'object' : obj | ||
, 'function' : lazy | ||
} | ||
|
||
function fromArray(a) { | ||
return arraySeq(a.slice(), 0) | ||
function vec(a) { | ||
if (!a.length) return nil | ||
|
||
const next = lim(a.length, function(i) { return a[i] }) | ||
|
||
return lazy(next, a.length) | ||
} | ||
|
||
function fromString(s) { | ||
const chars = [] | ||
function str(s) { | ||
const chr = [] | ||
|
||
for (var i = 0; i < s.length; i++) { | ||
var c = s.charCodeAt(i) | ||
|
||
if (lt(0xD800, c, 0xDBFF)) { | ||
chars.push(s.charAt(i) + s.charAt(++i)) | ||
chr.push(s.charAt(i) + s.charAt(++i)) | ||
} else { | ||
chars.push(s.charAt(i)) | ||
chr.push(s.charAt(i)) | ||
} | ||
} | ||
|
||
return arraySeq(chars, 0) | ||
return vec(chr) | ||
} | ||
|
||
function fromObject(x) { | ||
function obj(x) { | ||
const props = keys(x).map(function(k) { | ||
const o = {}; o[k] = x[k] | ||
return Object.freeze(o) | ||
}) | ||
|
||
return arraySeq(props, 0) | ||
return vec(props) | ||
} | ||
|
||
function arraySeq(a, i) { | ||
return make(a[i], next, a.length, a) | ||
|
||
function next() { | ||
return a[++i] | ||
} | ||
} | ||
function lazy(next, length) { | ||
const head = next() | ||
|
||
function make(first, next, size, desc) { | ||
if (!isStr(desc)) { | ||
desc = desc? src(desc) : '(lazy)' | ||
} | ||
if (head === nil) return nil | ||
|
||
const s = Object.create(seq, | ||
{ head : { value: head } | ||
, tail : { value: once(tail) } | ||
, length : { value: size } | ||
, valueOf : { value: head } | ||
, toString : { value: toString } | ||
return Object.create(seq, | ||
{ head : { get: constantly(head) } | ||
, tail : { get: once(partial(lazy, next, length)) } | ||
, length : { value: length } | ||
, valueOf : { value: constantly(head) } | ||
, toString : { value: constantly('[seq lazy]')} | ||
} | ||
) | ||
|
||
return Object.freeze(s) | ||
|
||
function head() { | ||
return first | ||
} | ||
|
||
function tail() { | ||
if (isNum(size) && size === 1) return null | ||
|
||
const rest = next.call(s) | ||
|
||
return rest === nil? null : seq(rest, next, size - 1) | ||
} | ||
|
||
function toString() { | ||
return desc | ||
} | ||
} |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
const partial = require('../lib/partial') | ||
, expect = require('chai').expect | ||
, each = require('../lib/each') | ||
, lim = require('../lib/lim') | ||
, nil = require('../lib/nil') | ||
|
||
describe('lim', function() { | ||
describe('when called with a limit `5` and a function `fn`', function() { | ||
const ltd = lim(5, function(n) { return n }) | ||
|
||
it('should return another function called `ltd`', function() { | ||
expect(ltd.name).to.equal('ltd') | ||
}) | ||
|
||
it('that should return `nil` after `5` calls', function() { | ||
each([0, 1, 2, 3, 4], function(n) { | ||
expect(ltd()).to.equal(n) | ||
}) | ||
|
||
expect(ltd()).to.equal(nil) | ||
}) | ||
}) | ||
|
||
describe('when called with an invalid limit', function() { | ||
it('should throw a TypeError', function() { | ||
each([-1, null, true, undefined, [], {}], function(n) { | ||
expect(partial(lim, n)).to.throw(TypeError) | ||
}) | ||
}) | ||
}) | ||
|
||
describe('when called without a function', function() { | ||
it('should throw a TypeError', function() { | ||
each([-1, null, true, undefined, [], {}], function(fn) { | ||
expect(partial(lim, 5, fn)).to.throw(TypeError) | ||
}) | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.
@sammyt do you reckon there's a better name for this function? I tried to find an FP precedent, but my google-fu is apparently not good enough. The point of the function is to limit execution of the function to a set number of times and any subsequent calls should return
nil
, regardless of input. The function is called with whatever arguments it expects, and an additional argument which is the number of times the function has been executed, starting with zero.This makes it simple to implement lazy for loops, where each iteration is a call to the function. I looked at
iterate
, but the difference – aside from not returning a sequence – is thatlim
functions will accept arbitrary arguments (but the first one always being an index) and isn't reliant on the return value of a previous iteration. Another function I looked at isrepeatedly
which is more in line with whatlim
does, but againlim
doesn't produce a sequence and because of that you can pass arguments tolim
functions.