Skip to content
This repository has been archived by the owner on Oct 24, 2023. It is now read-only.

Commit

Permalink
Refactored seq again; also added the lim function
Browse files Browse the repository at this point in the history
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
mstade committed Apr 5, 2014
1 parent 8da159a commit d8f1aa1
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 147 deletions.
9 changes: 4 additions & 5 deletions lib/each.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ module.exports = each
function each(s, it) {
s = seq(s)

if (isNil(s) || s.head() === nil) return

do {
it(s.head())
} while (s = s.tail())
while (!isNil(s)) {
it(s.head)
s = s.tail
}
}

const isNil = require('./isNil')
Expand Down
17 changes: 17 additions & 0 deletions lib/lim.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module.exports = lim

This comment has been minimized.

Copy link
@mstade

mstade Apr 6, 2014

Author Owner

@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 that lim 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 is repeatedly which is more in line with what lim does, but again lim doesn't produce a sequence and because of that you can pass arguments to lim functions.


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')
6 changes: 3 additions & 3 deletions lib/range.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ function range(start, end, step) {

const size = floor(abs(end - start) / step)

return seq(start, inc, size, '(range '+start+' '+end+' '+step+')')
return seq(next, size)

function inc() {
return this + step
function next(n) {
return start + step * n
}
}

Expand Down
124 changes: 49 additions & 75 deletions lib/seq.js
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
}
}
19 changes: 8 additions & 11 deletions lib/take.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,18 @@ module.exports = take
function take(n, coll) {
coll = seq(coll)

if (isNil(coll)) return seq(nil, nil, 0)

if (coll.length !== undefined) {
n = min(n, coll.length)
}

return seq(coll.head(), next, n)
return seq(lim(n, next))

function next() {
coll = coll.tail()
return isNil(coll)? nil : coll.head()
if (isNil(coll)) return nil

const head = coll.head
coll = coll.tail
return head
}
}

const isNil = require('./isNil')
, lim = require('./lim')
, nil = require('./nil')
, seq = require('./seq')
, min = Math.min
, seq = require('./seq')
39 changes: 39 additions & 0 deletions test/lim.js
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)
})
})
})
})

0 comments on commit d8f1aa1

Please sign in to comment.