Skip to content

Commit

Permalink
enabling R.scan to be used as transducer. (ramda#2817)
Browse files Browse the repository at this point in the history
* Add R.scan transducer tests

* R.scan transducer enabler: R.into should always call the @@transducer/init method

* enable R.scan as transducer

* pull upstream & resolve conflicts

* add docs about R.scan can act as a transducer
  • Loading branch information
xgbuils authored Mar 31, 2022
1 parent 5a7ccd5 commit ea8c8b1
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 30 deletions.
41 changes: 19 additions & 22 deletions source/internal/_flatCat.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,27 @@ import _xArrayReduce from './_xArrayReduce.js';
import _xReduce from './_xReduce.js';
import _xfBase from './_xfBase.js';

var preservingReduced = function(xf) {
return {
'@@transducer/init': _xfBase.init,
'@@transducer/result': function(result) {
return xf['@@transducer/result'](result);
},
'@@transducer/step': function(result, input) {
var ret = xf['@@transducer/step'](result, input);
return ret['@@transducer/reduced'] ? _forceReduced(ret) : ret;
}
};
var tInit = '@@transducer/init';
var tStep = '@@transducer/step';
var tResult = '@@transducer/result';
function XPreservingReduced(xf) {
this.xf = xf;
}
XPreservingReduced.prototype[tInit] = _xfBase.init;
XPreservingReduced.prototype[tResult] = _xfBase.result;
XPreservingReduced.prototype[tStep] = function(result, input) {
var ret = this.xf[tStep](result, input);
return ret['@@transducer/reduced'] ? _forceReduced(ret) : ret;
};

var _flatCat = function _xcat(xf) {
var rxf = preservingReduced(xf);
return {
'@@transducer/init': _xfBase.init,
'@@transducer/result': function(result) {
return rxf['@@transducer/result'](result);
},
'@@transducer/step': function(result, input) {
return !_isArrayLike(input) ? _xArrayReduce(rxf, result, [input]) : _xReduce(rxf, result, input);
}
};
function XFlatCat(xf) {
this.xf = new XPreservingReduced(xf);
}
XFlatCat.prototype[tInit] = _xfBase.init;
XFlatCat.prototype[tResult] = _xfBase.result;
XFlatCat.prototype[tStep] = function(result, input) {
return !_isArrayLike(input) ? _xArrayReduce(this.xf, result, [input]) : _xReduce(this.xf, result, input);
};
var _flatCat = function _xcat(xf) { return new XFlatCat(xf); };

export default _flatCat;
27 changes: 27 additions & 0 deletions source/internal/_xscan.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import _curry3 from './_curry3.js';
import _xfBase from './_xfBase.js';

var tInit = '@@transducer/init';
var tStep = '@@transducer/step';

function XScan(reducer, acc, xf) {
this.xf = xf;
this.f = reducer;
this.acc = acc;
}
XScan.prototype[tInit] = function() {
return this.xf[tStep](this.xf[tInit](), this.acc);
};
XScan.prototype['@@transducer/result'] = _xfBase.result;
XScan.prototype[tStep] = function(result, input) {
if (result['@@transducer/reduced']) {
return result;
}
this.acc = this.f(this.acc, input);
return this.xf[tStep](result, this.acc);
};

var _xscan = _curry3(function _xscan(reducer, acc, xf) {
return new XScan(reducer, acc, xf);
});
export default _xscan;
8 changes: 3 additions & 5 deletions source/into.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import _clone from './internal/_clone.js';
import _curry3 from './internal/_curry3.js';
import _isTransformer from './internal/_isTransformer.js';
import _xReduce from './internal/_xReduce.js';
Expand Down Expand Up @@ -44,9 +43,8 @@ import _stepCat from './internal/_stepCat.js';
* const intoArray = R.into([]);
* intoArray(transducer, numbers); //=> [2, 3]
*/
var into = _curry3(function into(acc, xf, list) {
return _isTransformer(acc)
? _xReduce(xf(acc), acc['@@transducer/init'](), list)
: _xReduce(xf(_stepCat(acc)), _clone(acc, false), list);
var into = _curry3(function into(acc, transducer, list) {
var xf = transducer(_isTransformer(acc) ? acc : _stepCat(acc));
return _xReduce(xf, xf['@@transducer/init'](), list);
});
export default into;
10 changes: 7 additions & 3 deletions source/scan.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import _curry3 from './internal/_curry3.js';
import _dispatchable from './internal/_dispatchable.js';
import _xscan from './internal/_xscan.js';


/**
* Scan is similar to [`reduce`](#reduce), but returns a list of successively
* reduced values from the left
* reduced values from the left.
*
* Acts as a transducer if a transformer is given in list position.
*
* @func
* @memberOf R
Expand All @@ -22,7 +26,7 @@ import _curry3 from './internal/_curry3.js';
* const factorials = R.scan(R.multiply, 1, numbers); //=> [1, 1, 2, 6, 24]
* @symb R.scan(f, a, [b, c]) = [a, f(a, b), f(f(a, b), c)]
*/
var scan = _curry3(function scan(fn, acc, list) {
var scan = _curry3(_dispatchable([], _xscan, function scan(fn, acc, list) {
var idx = 0;
var len = list.length;
var result = [acc];
Expand All @@ -32,5 +36,5 @@ var scan = _curry3(function scan(fn, acc, list) {
idx += 1;
}
return result;
});
}));
export default scan;
23 changes: 23 additions & 0 deletions test/scan.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
var R = require('../source/index.js');
var eq = require('./shared/eq.js');
var sinon = require('sinon');


describe('scan', function() {
var add = function(a, b) {return a + b;};
var mult = function(a, b) {return a * b;};
var double = function(x) {return 2 * x;};

it('scans simple functions over arrays with the supplied accumulator', function() {
eq(R.scan(add, 0, [1, 2, 3, 4]), [0, 1, 3, 6, 10]);
Expand All @@ -16,4 +18,25 @@ describe('scan', function() {
eq(R.scan(mult, 1, []), [1]);
});

it('works with transducers', function() {
eq(R.into([], R.scan(add, 0), [1, 2, 3, 4]), [0, 1, 3, 6, 10]);
eq(R.into([], R.scan(mult, 1), []), [1]);
});

it('composes with other transducers', function() {
eq(R.into([], R.compose(R.scan(mult, 1), R.take(2)), [1, 2, 3, 4]), [1, 1]);
eq(R.into([], R.compose(R.scan(mult, 1), R.map(double)), [1, 2, 3, 4]), [2, 2, 4, 12, 48]);
});

it('works lazily: taking 3 elements must call reducer twice', function() {
var reducer = sinon.spy();
R.into([], R.compose(R.scan(reducer, 0), R.take(3)), [1, 2, 3, 4]);
sinon.assert.calledTwice(reducer);
});

it('works lazily: taking 0 elements must call reducer 0 times', function() {
var reducer = sinon.spy();
R.into([], R.compose(R.scan(reducer, 0), R.take(0)), [1, 2, 3, 4]);
sinon.assert.notCalled(reducer);
});
});

0 comments on commit ea8c8b1

Please sign in to comment.