src/com/cognitect/transducers.js
// Copyright 2014-2015 Cognitect. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
"use strict";
goog.provide("com.cognitect.transducers");
// =============================================================================
// Build target config
/** @define {boolean} */
var TRANSDUCERS_DEV = true;
/** @define {boolean} */
var TRANSDUCERS_NODE_TARGET = false;
/** @define {boolean} */
var TRANSDUCERS_BROWSER_TARGET = false;
/** @define {boolean} */
var TRANSDUCERS_BROWSER_AMD_TARGET = false;
goog.scope(function() {
/**
* @class transducers
*/
var transducers = com.cognitect.transducers;
// =========================================================================
// Definitions
transducers.ITER_SYMBOL = typeof Symbol != "undefined" ? Symbol.iterator : "@@iterator";
/**
* The Transducer protocol
* @interface
*/
transducers.ITransformer = function() {};
/**
* @returns {Object}
*/
transducers.ITransformer.prototype["@@transducer/init"] = function() {};
/**
* @param {Object} result
* @returns {Object}
*/
transducers.ITransformer.prototype["@@transducer/result"] = function(result) {};
/**
* @param {Object} result
* @param {Object} input
* @returns {Object}
*/
transducers.ITransformer.prototype["@@transducer/step"] = function(result, input) {};
/**
* The IReduced interface
* @interface
*/
transducers.IReduced = function() {};
// =========================================================================
// Utilities
transducers.isString = function(x) {
return typeof x == "string";
};
if(typeof Array.isArray != "undefined") {
transducers.isArray = function(x) {
return Array.isArray(x);
}
} else {
transducers.isArray = function(x) {
return goog.typeOf(x) == "array";
}
}
transducers.isObject = function(x) {
return goog.typeOf(x) == "object";
};
transducers.isIterable = function(x) {
return x[transducers.ITER_SYMBOL] || x["next"];
};
transducers.slice = function(arrayLike, start, n) {
if(n == null) {
return Array.prototype.slice.call(arrayLike, start);
} else {
return Array.prototype.slice.call(arrayLike, start, n);
}
};
/**
* Take a predicate function and return its complement.
* @method transducers.complement
* @param {function} a predicate function
* @return {function} the complement predicate function
* @example
* var isEven = function(n) { return n % 2 == 0; };
* var isOdd = transducers.complement(isEven);
*/
transducers.complement = function(f) {
return function(varArgs) {
return !f.apply(null, transducers.slice(arguments, 0));
};
};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.Wrap = function(stepFn) {
this.stepFn = stepFn;
};
transducers.Wrap.prototype["@@transducer/init"] = function() {
throw new Error("init not implemented");
};
transducers.Wrap.prototype["@@transducer/result"] = function(result) {
return result;
};
transducers.Wrap.prototype["@@transducer/step"] = function(result, input) {
return this.stepFn(result, input);
};
/**
* Take a two-arity reducing function where the first argument is the
* accumluation and the second argument is the next input and convert
* it into a transducer transformer object.
* @method transducers.wrap
* @param {function} stepFn a two-arity reducing function
* @return {com.cognitect.transducers.Wrap} a transducer transformer object
* @example
* var t = transducers;
* var arrayPush = t.wrap(function(arr, x) { arr.push(x); return arr; });
*/
transducers.wrap = function(stepFn) {
if(typeof stepFn == "function") {
return new transducers.Wrap(stepFn);
} else {
return stepFn;
}
};
// =========================================================================
// Main
/**
* @constructor
* @implements {com.cognitect.transducers.IReduced}
*/
transducers.Reduced = function(value) {
this["@@transducer/reduced"] = true;
this["@@transducer/value"] = value;
};
/**
* Return a reduced value. Reduced values short circuit transduce.
* @method transducers.reduced
* @param {Object} x any JavaScript value
* @return {com.cognitect.transducers.IReduced} a reduced value
* @example
* var reduced = transducers.reduced(1);
*/
transducers.reduced = function(x) {
return new transducers.Reduced(x);
};
/**
* Check if a value is reduced.
* @method transducers.isReduced
* @param {Object} x any JavaScript value
* @return {Boolean} true if the value is an instance of transducers.Reduced
* false otherwise
* @example
* var t = transducers;
* t.isReduced(1); // false
* t.isReduced(t.reduced(1)); // true
*/
transducers.isReduced = function(x) {
return (x instanceof transducers.Reduced) || (x && x["@@transducer/reduced"]);
};
/**
* Ensure that a value is reduced. If already reduced will not re-wrap.
* @method transducers.ensureReduced
* @param {Object} x any JavaScript value
* @return {com.cognitect.transducers.IReduced} a reduced value.
* @example
* var t = transducers;
* var x = t.ensureReduced(1);
* var y = t.ensureReduced(x);
* x === y; // true
*/
transducers.ensureReduced = function(x) {
if(transducers.isReduced(x)) {
return x;
} else {
return transducers.reduced(x);
}
};
transducers.deref = function(x) {
return x["@@transducer/value"];
};
/**
* Ensure a value is not reduced. Unwraps if reduced.
* @method transducers.unreduced
* @param {Object} x any JavaScript value
* @return {Object} a JavaScript value
* @example
* var t = transducers;
* var x = t.reduced(1);
* t.unreduced(x); // 1
* t.unreduced(t.unreduced(x)); // 1
*/
transducers.unreduced = function(x) {
if(transducers.isReduced(x)) {
return transducers.deref(x);
} else {
return x;
}
};
/**
* Identity function.
* @method transducers.identity
* @param {Object} x any JavaScript value
* @return {Object} a JavaScript value
* @example
* transducers.identity(1); // 1
*/
transducers.identity = function(x) {
return x;
};
/**
* Function composition. Take N function and return their composition.
* @method transducers.comp
* @param {Function} varArgs N functions
* @result {Function} a function that represent the composition of the arguments.
* @example
* var t = transducers;
* var inc = function(n) { return n + 1 };
* var double = function(n) { return n * 2 };
* var incDouble = t.comp(double, inc);
* incDouble(3); // 8
*/
transducers.comp = function(varArgs) {
var arglen = arguments.length;
if(arglen == 2) {
var f = arguments[0],
g = arguments[1];
return function(varArgs) {
return f(g.apply(null, transducers.slice(arguments, 0)));
};
} if(arglen > 2) {
return transducers.reduce(transducers.comp, arguments[0], transducers.slice(arguments, 1));
} else {
if(TRANSDUCERS_DEV) {
throw new Error("comp must given at least 2 arguments");
}
}
};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.Map = function(f, xf) {
this.f = f;
this.xf = xf;
};
transducers.Map.prototype["@@transducer/init"] = function() {
return this.xf["@@transducer/init"]();
};
transducers.Map.prototype["@@transducer/result"] = function(result) {
return this.xf["@@transducer/result"](result);
};
transducers.Map.prototype["@@transducer/step"] = function(result, input) {
return this.xf["@@transducer/step"](result, this.f(input));
};
/**
* Mapping transducer constructor
* @method transducers.map
* @param {Function} f the mapping operation
* @return {com.cognitect.transducers.Map} returns a mapping transducer
* @example
* var t = transducers;
* var inc = function(n) { return n+1; };
* var xf = t.map(inc);
* t.into([], xf, [1,2,3]); // [2,3,4]
*/
transducers.map = function(f) {
if(TRANSDUCERS_DEV && (f == null)) {
throw new Error("At least one argument must be supplied to map");
} else {
return function(xf) {
return new transducers.Map(f, xf);
};
}
};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.Filter = function(pred, xf) {
this.pred = pred;
this.xf = xf;
};
transducers.Filter.prototype["@@transducer/init"] = function() {
return this.xf["@@transducer/init"]();
};
transducers.Filter.prototype["@@transducer/result"] = function(result) {
return this.xf["@@transducer/result"](result);
};
transducers.Filter.prototype["@@transducer/step"] = function(result, input) {
if(this.pred(input)) {
return this.xf["@@transducer/step"](result, input);
} else {
return result;
}
};
/**
* Filtering transducer constructor
* @method transducers.filter
* @param {Function} pred a predicate function
* @return {com.cognitect.transducers.Filter} returns a filtering transducer
* @example
* var t = transducers;
* var isEven = function(n) { return n % 2 == 0; };
* var xf = t.filter(isEven);
* t.into([], xf, [0,1,2,3,4]); // [0,2,4];
*/
transducers.filter = function(pred) {
if(TRANSDUCERS_DEV && (typeof pred != "function")) {
throw new Error("filter must be given a function");
} else {
return function(xf) {
return new transducers.Filter(pred, xf);
};
}
};
/**
* Similar to filter except the predicate is used to
* eliminate values.
* @method transducers.remove
* @param {Function} pred a predicate function
* @return {com.cognitect.transducers.Filter} returns a removing transducer
* @example
* var t = transducers;
* var isEven = function(n) { return n % 2 == 0; };
* var xf = t.remove(isEven);
* t.into([], xf, [0,1,2,3,4]); // [1,3];
*/
transducers.remove = function(pred) {
if(TRANSDUCERS_DEV && (typeof pred != "function")) {
throw new Error("remove must be given a function");
} else {
return transducers.filter(transducers.complement(pred));
}
};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.Take = function(n, xf) {
this.n = n;
this.xf = xf;
};
transducers.Take.prototype["@@transducer/init"] = function() {
return this.xf["@@transducer/init"]();
};
transducers.Take.prototype["@@transducer/result"] = function(result) {
return this.xf["@@transducer/result"](result);
};
transducers.Take.prototype["@@transducer/step"] = function(result, input) {
if(this.n > 0) {
result = this.xf["@@transducer/step"](result, input);
} else {
result = transducers.ensureReduced(result);
}
this.n--;
return result;
};
/**
* A take transducer constructor. Will take n values before
* returning a reduced result.
* @method transducers.take
* @param {Number} n the number of inputs to receive.
* @return {com.cognitect.transducers.Take} a take transducer
* @example
* var t = transducers;
* var xf = t.take(3);
* t.into([], xf, [0,1,2,3,4,5]); // [0,1,2];
*/
transducers.take = function(n) {
if(TRANSDUCERS_DEV && (typeof n != "number")) {
throw new Error("take must be given an integer");
} else {
return function(xf) {
return new transducers.Take(n, xf);
};
}
};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.TakeWhile = function(pred, xf) {
this.pred = pred;
this.xf = xf;
};
transducers.TakeWhile.prototype["@@transducer/init"] = function() {
return this.xf["@@transducer/init"]();
};
transducers.TakeWhile.prototype["@@transducer/result"] = function(result) {
return this.xf["@@transducer/result"](result);
};
transducers.TakeWhile.prototype["@@transducer/step"] = function(result, input) {
if(this.pred(input)) {
return this.xf["@@transducer/step"](result, input);
} else {
return transducers.reduced(result);
}
};
/**
* Like the take transducer except takes as long as the pred
* return true for inputs.
* @method transducers.takeWhile
* @param {Function} pred a predicate function
* @return {com.cognitect.transducers.TakeWhile} a takeWhile transducer
* @example
* var t = transducers;
* var xf = t.takeWhile(function(n) { return n < 3; });
* t.into([], xf, [0,1,2,3,4,5]); // [0,1,2];
*/
transducers.takeWhile = function(pred) {
if(TRANSDUCERS_DEV && (typeof pred != "function")) {
throw new Error("takeWhile must given a function");
} else {
return function(xf) {
return new transducers.TakeWhile(pred, xf);
};
}
};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.TakeNth = function(n, xf) {
this.i = -1;
this.n = n;
this.xf = xf;
};
transducers.TakeNth.prototype["@@transducer/init"] = function() {
return this.xf["@@transducer/init"]();
};
transducers.TakeNth.prototype["@@transducer/result"] = function(result) {
return this.xf["@@transducer/result"](result);
};
transducers.TakeNth.prototype["@@transducer/step"] = function(result, input) {
this.i++;
if((this.i % this.n) == 0) {
return this.xf["@@transducer/step"](result, input);
} else {
return result;
}
};
/**
* A transducer that takes every Nth input
* @method transducers.takeNth
* @param {Number} n an integer
* @return {com.cognitect.transducers.TakeNth} a takeNth transducer
* @example
* var t = transducers;
* var xf = t.takeNth(3);
* t.into([], xf, [0,1,2,3,4,5]); // [2,5];
*/
transducers.takeNth = function(n) {
if(TRANSDUCERS_DEV && (typeof n != "number")) {
throw new Error("takeNth must be given a number");
} else {
return function(xf) {
return new transducers.TakeNth(n, xf);
};
}
};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.Drop = function(n, xf) {
this.n = n;
this.xf = xf;
};
transducers.Drop.prototype["@@transducer/init"] = function() {
return this.xf["@@transducer/init"]();
};
transducers.Drop.prototype["@@transducer/result"] = function(result) {
return this.xf["@@transducer/result"](result);
};
transducers.Drop.prototype["@@transducer/step"] = function(result, input) {
if(this.n > 0) {
this.n--;
return result;
} else {
return this.xf["@@transducer/step"](result, input);
}
};
/**
* A dropping transducer constructor
* @method transducers.drop
* @param {Number} n an integer, the number of inputs to drop.
* @return {com.cognitect.transducers.Drop} a dropping transducer
* @example
* var t = transducers;
* var xf = t.drop(3);
* t.into([], xf, [0,1,2,3,4,5]); // [3,4,5];
*/
transducers.drop = function(n) {
if(TRANSDUCERS_DEV && (typeof n !== "number")) {
throw new Error("drop must be given an integer");
} else {
return function(xf) {
return new transducers.Drop(n, xf);
};
}
};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.DropWhile = function(pred, xf) {
this.drop = true;
this.pred = pred;
this.xf = xf;
};
transducers.DropWhile.prototype["@@transducer/init"] = function() {
return this.xf["@@transducer/init"]();
};
transducers.DropWhile.prototype["@@transducer/result"] = function(result) {
return this.xf["@@transducer/result"](result);
};
transducers.DropWhile.prototype["@@transducer/step"] = function(result, input) {
if(this.drop && this.pred(input)) {
return result;
} else {
if(this.drop) this.drop = false;
return this.xf["@@transducer/step"](result, input);
}
};
/**
* A dropping transducer that drop inputs as long as
* pred is true.
* @method transducers.dropWhile
* @param {Function} pred a predicate function
* @return {com.cognitect.transducers.DropWhile} a dropWhile transducer
* @example
* var t = transducers;
* var xf = t.dropWhile(function(n) { return n < 3; });
* t.into([], xf, [0,1,2,3,4,5]); // [3,4,5];
*/
transducers.dropWhile = function(pred) {
if(TRANSDUCERS_DEV && (typeof pred != "function")) {
throw new Error("dropWhile must be given a function");
} else {
return function(xf) {
return new transducers.DropWhile(pred, xf);
};
}
};
transducers.NONE = {};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.PartitionBy = function(f, xf) {
this.f = f;
this.xf = xf;
this.a = [];
this.pval = transducers.NONE;
};
transducers.PartitionBy.prototype["@@transducer/init"] = function() {
return this.xf["@@transducer/init"]()
};
transducers.PartitionBy.prototype["@@transducer/result"] = function(result) {
if(this.a.length > 0) {
result = transducers.unreduced(this.xf["@@transducer/step"](result, this.a));
this.a = [];
}
return this.xf["@@transducer/result"](result);
};
transducers.PartitionBy.prototype["@@transducer/step"] = function(result, input) {
var pval = this.pval,
val = this.f(input);
this.pval = val;
// NOTE: we should probably allow someone to define
// equality? - David
if((pval == transducers.NONE) ||
(pval == val)) {
this.a.push(input);
return result;
} else {
var ret = this.xf["@@transducer/step"](result, this.a);
this.a = [];
if(!transducers.isReduced(ret)) {
this.a.push(input);
}
return ret;
}
};
/**
* A partitioning transducer. Collects inputs into
* arrays as long as predicate remains true for contiguous
* inputs.
* @method transducers.partitionBy
* @param {Function} f a partition function. When the result
* for an input changes from the previous result will create
* a partition.
* @return {com.cognitect.transducers.PartitionBy} a partitionBy transducer
* @example
* var t = transducers;
* var xf = t.partitionBy(function(x) { return typeof x == "string"; });
* t.into([], xf, [0,1,"foo","bar",2,3,"bar","baz"]); // [[0,1],["foo","bar"],[2,3],["bar","baz"]];
*/
transducers.partitionBy = function(f) {
if(TRANSDUCERS_DEV && (typeof f != "function")) {
throw new Error("partitionBy must be given an function");
} else {
return function(xf) {
return new transducers.PartitionBy(f, xf);
};
}
};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.PartitionAll = function(n, xf) {
this.n = n;
this.xf = xf;
this.a = [];
};
transducers.PartitionAll.prototype["@@transducer/init"] = function() {
return this.xf["@@transducer/init"]();
};
transducers.PartitionAll.prototype["@@transducer/result"] = function(result) {
if(this.a.length > 0) {
result = transducers.unreduced(this.xf["@@transducer/step"](result, this.a));
this.a = [];
}
return this.xf["@@transducer/result"](result);
};
transducers.PartitionAll.prototype["@@transducer/step"] = function(result, input) {
this.a.push(input);
if(this.n == this.a.length) {
var a = this.a;
this.a = [];
return this.xf["@@transducer/step"](result, a);
} else {
return result;
}
};
/**
* A partitioning transducer. Collects inputs into
* arrays of size N.
* @method transducers.partitionAll
* @param {Number} n an integer
* @return {com.cognitect.transducers.PartitionAll} a partitionAll transducer
* @example
* var t = transducers;
* var xf = t.partitionAll(3);
* t.into([], xf, [0,1,2,3,4,5]); // [[0,1,2],[3,4,5]]
*/
transducers.partitionAll = function(n) {
if(TRANSDUCERS_DEV && (typeof n != "number")) {
throw new Error("partitionAll must be given a number");
} else {
return function(xf) {
return new transducers.PartitionAll(n, xf);
};
}
};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.Keep = function(f, xf) {
this.f = f;
this.xf = xf;
};
transducers.Keep.prototype["@@transducer/init"] = function() {
return this.xf["@@transducer/init"]();
};
transducers.Keep.prototype["@@transducer/result"] = function(result) {
return this.xf["@@transducer/result"](result);
};
transducers.Keep.prototype["@@transducer/step"] = function(result, input) {
var v = this.f(input);
if(v == null) {
return result;
} else {
return this.xf["@@transducer/step"](result, input);
}
};
/**
* A keeping transducer. Keep inputs as long as the provided
* function does not return null or undefined.
* @method transducers.keep
* @param {Function} f a function
* @return {com.cognitect.transducers.Keep} a keep transducer
* @example
* var t = transducers;
* var xf = t.keep(function(x) { if(typeof x == "string") return "cool"; });
* t.into([], xf, [0,1,"foo",3,4,"bar"]); // ["foo","bar"]
*/
transducers.keep = function(f) {
if(TRANSDUCERS_DEV && (typeof f != "function")) {
throw new Error("keep must be given a function");
} else {
return function(xf) {
return new transducers.Keep(f, xf);
};
}
};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.KeepIndexed = function(f, xf) {
this.i = -1;
this.f = f;
this.xf = xf;
};
transducers.KeepIndexed.prototype["@@transducer/init"] = function() {
return this.xf["@@transducer/init"]();
};
transducers.KeepIndexed.prototype["@@transducer/result"] = function(result) {
return this.xf["@@transducer/result"](result);
};
transducers.KeepIndexed.prototype["@@transducer/step"] = function(result, input) {
this.i++;
var v = this.f(this.i, input);
if(v == null) {
return result;
} else {
return this.xf["@@transducer/step"](result, input);
}
};
/**
* Like keep but the provided function will be passed the
* index as the second argument.
* @method transducers.keepIndexed
* @param {Function} f a function
* @return {com.cognitect.transducers.KeepIndexed} a keepIndexed transducer
* @example
* var t = transducers;
* var xf = t.keepIndexed(function(i, x) { if(typeof x == "string") return "cool"; });
* t.into([], xf, [0,1,"foo",3,4,"bar"]); // ["foo","bar"]
*/
transducers.keepIndexed = function(f) {
if(TRANSDUCERS_DEV && (typeof f != "function")) {
throw new Error("keepIndexed must be given a function");
} else {
return function(xf) {
return new transducers.KeepIndexed(f, xf);
};
}
};
/**
* Given a transformer returns a transformer which preserves
* reduced by wrapping one more time. See cat.
* @method transducers.preservingReduced
* @param {com.cognitect.transducers.ITransformer} xf a transformer
* @return {com.cognitect.transducers.ITransformer} a transformer which preserves reduced
*/
transducers.preservingReduced = function(xf) {
return {
"@@transducer/init": function() {
return xf["@@transducer/init"]();
},
"@@transducer/result": function(result) {
return result;
},
"@@transducer/step": function(result, input) {
var ret = xf["@@transducer/step"](result, input);
if(transducers.isReduced(ret)) {
return transducers.reduced(ret);
} else {
return ret;
}
}
};
};
/**
* Given a transformer return a concatenating transformer
* @method transducers.cat
* @param {com.cognitect.transducers.ITransformer} xf a transformer
* @return {com.cognitect.transducers.ITransformer} a concatenating transformer
*/
transducers.cat = function(xf) {
var rxf = transducers.preservingReduced(xf);
return {
"@@transducer/init": function() {
return xf["@@transducer/init"]();
},
"@@transducer/result": function(result) {
return xf["@@transducer/result"](result);
},
"@@transducer/step": function(result, input) {
return transducers.reduce(rxf, result, input);
}
};
};
/**
* A mapping concatenating transformer
* @method transducers.mapcat
* @param {Function} f the mapping function
* @return {com.cognitect.transducers.ITransformer} a mapping concatenating transducer
* @example
* var t = transducers;
* var reverse = function(arr) { var arr = Array.prototype.slice.call(arr, 0); arr.reverse(); return arr; }
* var xf = t.mapcat(reverse);
* t.into([], xf, [[3,2,1],[6,5,4]]); // [1,2,3,4,5,6]
*/
transducers.mapcat = function(f) {
return transducers.comp(transducers.map(f), transducers.cat);
};
/**
* @param {com.cognitect.transducers.ITransformer} xf
* @param {Object} init
* @param {String} string
* @returns {*}
*/
transducers.stringReduce = function(xf, init, string) {
var acc = init;
for(var i = 0; i < string.length; i++) {
acc = xf["@@transducer/step"](acc, string.charAt(i));
if(transducers.isReduced(acc)) {
acc = transducers.deref(acc);
break;
}
}
return xf["@@transducer/result"](acc);
};
/**
* @param {com.cognitect.transducers.ITransformer} xf
* @param {Object} init
* @param {Array} array
* @returns {*}
*/
transducers.arrayReduce = function(xf, init, array) {
var acc = init;
for(var i = 0; i < array.length; i++) {
acc = xf["@@transducer/step"](acc, array[i]);
if(transducers.isReduced(acc)) {
acc = transducers.deref(acc);
break;
}
}
return xf["@@transducer/result"](acc);
};
/**
* @param {com.cognitect.transducers.ITransformer} xf
* @param {Object} init
* @param {Object} obj
* @returns {*}
*/
transducers.objectReduce = function(xf, init, obj) {
var acc = init;
for(var p in obj) {
if(obj.hasOwnProperty(p)) {
acc = xf["@@transducer/step"](acc, [p, obj[p]]);
if(transducers.isReduced(acc)) {
acc = transducers.deref(acc);
break;
}
}
}
return xf["@@transducer/result"](acc);
};
/**
* @param {com.cognitect.transducers.ITransformer} xf
* @param {Object} init
* @param {Object} iter
* @returns {*}
*/
transducers.iterableReduce = function(xf, init, iter) {
if(iter[transducers.ITER_SYMBOL]) {
iter = iter[transducers.ITER_SYMBOL]();
}
var acc = init,
step = iter.next();
while(!step.done) {
acc = xf["@@transducer/step"](acc, step.value);
if(transducers.isReduced(acc)) {
acc = transducers.deref(acc);
break;
}
step = iter.next();
}
return xf["@@transducer/result"](acc);
};
/**
* Given a transducer, an intial value and a
* collection - returns the reduction.
* @method transducers.reduce
* @param {com.cognitect.transducers.ITransformer|Function} xf a transducer or two-arity function
* @param {Object} init any JavaScript value
* @param {String|Array|Object} coll any iterable JavaScript value
* @return {Object} an iterable JavaScript value: string, array
* iterable, or object.
*/
transducers.reduce = function(xf, init, coll) {
if(coll) {
xf = typeof xf == "function" ? transducers.wrap(xf) : xf;
if(transducers.isString(coll)) {
return transducers.stringReduce(xf, init, coll);
} else if(transducers.isArray(coll)) {
return transducers.arrayReduce(xf, init, coll);
} else if(transducers.isIterable(coll)) {
return transducers.iterableReduce(xf, init, coll);
} else if(transducers.isObject(coll)) {
return transducers.objectReduce(xf, init, coll);
} else {
throw new Error("Cannot reduce instance of " + coll.constructor.name);
}
}
};
/**
* Given a transducer, a builder function, an initial value
* and a iterable collection - returns the reduction.
* collection - returns the reduction.
* @method transducers.transduce
* @param {com.cognitect.transducers.ITransformer} xf a transducer
* @param {com.cognitect.transducers.ITransformer|Function} f a transducer or two-arity function
* @param {Object=} init any JavaScript value
* @param {String|Array|Object} coll any iterable JavaScript value
* @return {Object} a JavaScript value.
* @example
* var t = transducers;
* var inc = function(n) { return n+1; };
* var isEven = function(n) { return n % 2 == 0; };
* var apush = function(arr,x) { arr.push(x); return arr; };
* var xf = t.comp(t.map(inc),t.filter(isEven));
* t.transduce(xf, apush, [], [1,2,3,4]); // [2,4]
*/
transducers.transduce = function(xf, f, init, coll) {
if(arguments.length == 3) {
coll = init;
if(typeof f == "function") {
throw new Error("If given only three arguments f must satisfy "+
"the ITransformer interface.");
} else {
init = f["@@transducer/init"]();
}
}
f = typeof f == "function" ? transducers.wrap(f) : f;
xf = xf(f);
return transducers.reduce(xf, init, coll);
};
transducers.stringAppend = function(string, x) {
return string + x;
};
transducers.arrayPush = function(arr, x) {
arr.push(x);
return arr;
};
transducers.addEntry = function(obj, entry) {
obj[entry[0]] = entry[1];
return obj;
};
/**
* Reduce a value into the given empty value using a transducer.
* @method transducers.into
* @param {String|Array|Object} empty a JavaScript collection
* @param {com.cognitect.transducers.ITransformer} xf a transducer
* @param {Object} coll any iterable JavaScript value: array, string,
* object, or iterable.
* @return {Object} a JavaScript value.
* @example
* var t = transducers;
* var inc = function(n) { return n+1; };
* var isEven = function(n) { return n % 2 == 0; };
* var apush = function(arr,x) { arr.push(x); return arr; };
* var xf = t.comp(t.map(inc),t.filter(isEven));
* t.into([], xf, [1,2,3,4]); // [2,4]
*/
transducers.into = function(empty, xf, coll) {
if(transducers.isString(empty)) {
return transducers.transduce(xf, transducers.stringAppend, empty, coll);
} else if(transducers.isArray(empty)) {
return transducers.transduce(xf, transducers.arrayPush, empty, coll);
} else if(transducers.isObject(empty)) {
return transducers.transduce(xf, transducers.addEntry, empty, coll);
}
};
/**
* @constructor
* @implements {com.cognitect.transducers.ITransformer}
*/
transducers.Completing = function(cf, xf) {
this.cf = cf;
this.xf = xf;
};
transducers.Completing.prototype["@@transducer/init"] = function() {
return this.xf["@@transducer/init"]();
};
transducers.Completing.prototype["@@transducer/result"] = function(result) {
return this.cf(result);
};
transducers.Completing.prototype["@@transducer/step"] = function(result, step) {
return this.xf["@@transducer/step"](result, step);
};
/**
* A completing transducer constructor. Useful to provide cleanup
* logic at the end of a reduction/transduction.
* @method transducers.completing
* @param {com.cognitect.transducers.ITransformer} xf a transducer
* @param {Function} cf a function to apply at the end of the reduction/transduction
* @return {com.cognitect.transducers.ITransformer} a transducer
*/
transducers.completing = function(xf, cf) {
xf = typeof xf == "function" ? transducers.wrap(xf) : xf;
cf = cf || transducers.identity;
if(TRANSDUCERS_DEV && (xf != null) && !transducers.isObject(xf)) {
throw new Error("completing must be given a transducer as first argument");
} else {
return new transducers.Completing(cf, xf);
}
};
/**
* Convert a transducer transformer object into a function so
* that it can be used with existing reduce implementation i.e. native,
* Underscore, lodash
* @method transducers.toFn
* @param {com.cognitect.transducers.ITransformer} xf a transducer
* @param {Function} builder a function which take the accumulator and the
* the next input and return a new accumulator value.
* @return {Function} a two-arity function compatible with existing reduce
* implementations
* @example
* var t = transducers;
* var arr = [0,1,2,3,4,5],
* var apush = function(arr, x) { arr.push(x); return arr; },
* var xf = t.comp(t.map(inc),t.filter(isEven));
* arr.reduce(t.toFn(xf, apush), []); // [2,4,6]
*/
transducers.toFn = function(xf, builder) {
if(typeof builder == "function") {
builder = transducers.wrap(builder);
}
var rxf = xf(builder);
return rxf["@@transducer/step"].bind(rxf);
};
// =============================================================================
// Utilities
/**
* A transformer which simply returns the first input.
* @method transducers.first
* @return {com.cognitect.transducers.ITransformer} a transducer transformer
*/
transducers.first = transducers.wrap(function(result, input) {
return transducers.reduced(input);
});
// =============================================================================
// Exporting
if(TRANSDUCERS_BROWSER_TARGET) {
goog.exportSymbol("transducers.reduced", transducers.reduced);
goog.exportSymbol("transducers.isReduced", transducers.isReduced);
goog.exportSymbol("transducers.comp", transducers.comp);
goog.exportSymbol("transducers.complement", transducers.complement);
goog.exportSymbol("transducers.identity", transducers.identity);
goog.exportSymbol("transducers.transduce", transducers.transduce);
goog.exportSymbol("transducers.reduce", transducers.reduce);
goog.exportSymbol("transducers.map", transducers.map);
goog.exportSymbol("transducers.Map", transducers.Map);
goog.exportSymbol("transducers.filter", transducers.filter);
goog.exportSymbol("transducers.Filter", transducers.Filter);
goog.exportSymbol("transducers.remove", transducers.remove);
goog.exportSymbol("transducers.Remove", transducers.Remove);
goog.exportSymbol("transducers.keep", transducers.keep);
goog.exportSymbol("transducers.Keep", transducers.Keep);
goog.exportSymbol("transducers.keepIndexed", transducers.keepIndexed);
goog.exportSymbol("transducers.KeepIndexed", transducers.KeepIndexed);
goog.exportSymbol("transducers.take", transducers.take);
goog.exportSymbol("transducers.Take", transducers.Take);
goog.exportSymbol("transducers.takeWhile", transducers.takeWhile);
goog.exportSymbol("transducers.TakeWhile", transducers.TakeWhile);
goog.exportSymbol("transducers.takeNth", transducers.takeNth);
goog.exportSymbol("transducers.TakeNth", transducers.TakeNth);
goog.exportSymbol("transducers.drop", transducers.drop);
goog.exportSymbol("transducers.Drop", transducers.Drop);
goog.exportSymbol("transducers.dropWhile", transducers.dropWhile);
goog.exportSymbol("transducers.DropWhile", transducers.DropWhile);
goog.exportSymbol("transducers.partitionBy", transducers.partitionBy);
goog.exportSymbol("transducers.PartitionBy", transducers.PartitionBy);
goog.exportSymbol("transducers.partitionAll", transducers.partitionAll);
goog.exportSymbol("transducers.PartitionAll", transducers.PartitionAll);
goog.exportSymbol("transducers.completing", transducers.completing);
goog.exportSymbol("transducers.Completing", transducers.Completing);
goog.exportSymbol("transducers.wrap", transducers.wrap);
goog.exportSymbol("transducers.Wrap", transducers.Wrap);
goog.exportSymbol("transducers.cat", transducers.cat);
goog.exportSymbol("transducers.mapcat", transducers.mapcat);
goog.exportSymbol("transducers.into", transducers.into);
goog.exportSymbol("transducers.toFn", transducers.toFn);
goog.exportSymbol("transducers.first", transducers.first);
goog.exportSymbol("transducers.ensureReduced", transducers.ensureReduced);
goog.exportSymbol("transducers.unreduced", transducers.unreduced);
goog.exportSymbol("transducers.deref", transducers.deref);
}
if(TRANSDUCERS_NODE_TARGET) {
module.exports = {
reduced: transducers.reduced,
isReduced: transducers.isReduced,
comp: transducers.comp,
complement: transducers.complement,
identity: transducers.identity,
map: transducers.map,
Map: transducers.Map,
filter: transducers.filter,
Filter: transducers.Filter,
remove: transducers.remove,
Remove: transducers.Remove,
keep: transducers.keep,
Kemove: transducers.Keep,
keepIndexed: transducers.keepIndexed,
KeepIndexed: transducers.KeepIndexed,
take: transducers.take,
Take: transducers.Take,
takeWhile: transducers.takeWhile,
TakeWhile: transducers.TakeWhile,
takeNth: transducers.takeNth,
TakeNth: transducers.TakeNth,
drop: transducers.drop,
Drop: transducers.Drop,
dropWhile: transducers.dropWhile,
DropWhile: transducers.DropWhile,
partitionBy: transducers.partitionBy,
PartitionBy: transducers.PartitionBy,
partitionAll: transducers.partitionAll,
PartitionAll: transducers.PartitionAll,
completing: transducers.completing,
Completing: transducers.Completing,
wrap: transducers.wrap,
Wrap: transducers.Wrap,
cat: transducers.cat,
mapcat: transducers.mapcat,
transduce: transducers.transduce,
reduce: transducers.reduce,
into: transducers.into,
toFn: transducers.toFn,
first: transducers.first,
ensureReduced: transducers.ensureReduced,
unreduced: transducers.unreduced,
deref: transducers.deref
};
}
});