/*
* Copyright (c) 2017 Thomas Otterson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/**
* Transducers for using functions to determine new values of collection elements.
*
* @module map
* @private
*/
const { protocols } = require('../modules/protocol');
const { sequence, compose } = require('../modules/transformation');
const { isFunction } = require('../modules/util');
const { flatten } = require('./core');
const p = protocols;
/**
* A transducer function that is returned by the `{@link module:xduce.transducers.map|map}` transducer.
*
* @private
*
* @param {function} fn A single-parameter function which is supplied each input collection element in turn. The return
* values of these calls become the elements of the output collection.
* @param {module:xduce~transducerObject} xform The transducer object that the new one should be chained to.
* @return {module:xduce~transducerObject} A new transducer object, performing no transformation and chaining to the
* provided transducer object.
*/
function mapTransducer(fn, xform) {
return {
[p.init]() {
return xform[p.init]();
},
[p.step](acc, input) {
return xform[p.step](acc, fn(input));
},
[p.result](value) {
return xform[p.result](value);
}
};
}
/**
* **Creates a new collection whose values are the results of mapping input collection elements over a function.**
*
* If no collection is provided, a function is returned that can be passed to `{@link module:xduce.sequence|sequence}`,
* et al.
*
* ```
* const result = map([1, 2, 3, 4, 5], x => x * x);
* // result = [1, 4, 9, 16, 25]
* ```
*
* @memberof module:xduce.transducers
*
* @param {*} [collection] An optional input collection that is to be transduced.
* @param {function} fn A function that is supplied each input collection element in turn. The return values of this
* function become the elements of the output collection.
* @param {object} [ctx] An optional context object which is set to `this` for the function `fn`. This does not work if
* `fn` is an arrow function, as they cannot be bound to contexts.
* @return {(*|module:xduce~transducerFunction)} If a collection is supplied, then the function returns a new
* collection of the same type containing the return values of `fn` when passed those elements. If no collection is
* supplied, a transducer function, suitable for passing to `{@link module:xduce.sequence|sequence}`,
* `{@link module:xduce.into|into}`, etc. is returned.
*/
function map(collection, fn, ctx) {
const [col, func] = isFunction(collection) ? [null, collection.bind(fn)] : [collection, fn.bind(ctx)];
return col ? sequence(col, map(func)) : xform => mapTransducer(func, xform);
}
/**
* **A map function that flattens any collections among the return values.**
*
* This is a composition of `{@link module:xduce.transducers.map|map}` and
* `{@link module:xduce.transducers.flatten|flatten}`. In fact it could be defined by the user by using those two
* functions with `{@link module:xduce.compose|compose}`, but the concept of a flatmap is so fundamental that it's
* included separately.
*
* Because the map is followed by flattening, there are the same notes as with
* `{@link module:xduce.transducers.flatten|flatten}`; this function doesn't make a lot of sense with functions that
* return objects, strings, or iterators.
*
* If no collection is provided, a function is returned that can be passed to `{@link module:xduce.sequence|sequence}`,
* et al.
*
* ```
* const duplicate = x => [x, x];
*
* let result = flatMap([1, 2, 3, 4, 5], duplicate);
* // result = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
*
* // The following is equivalent
* const fn = compose(map(duplicate), flatten());
* result = sequence([1, 2, 3, 4, 5], fn);
* // result = [1, 1, 2, 2, 3, 3, 4, 4, 5, 5]
*
* // To illustrate the difference from `map`, here's what `map` would do with
* // the same parameters
* result = map([1, 2, 3, 4, 5], duplicate);
* // result = [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]
* ```
*
* @memberof module:xduce.transducers
*
* @param {*} [collection] An optional input collection that is to be transduced.
* @param {function} fn A function that is supplied each input collection element in turn. The return values of this
* function are flattened to become the elements of the output collection.
* @param {object} [ctx] An optional context object which is set to `this` for the function `fn`. This does not work if
* `fn` is an arrow function, as they cannot be bound to contexts.
* @return {(*|module:xduce~transducerFunction)} If a collection is supplied, then the function returns a new
* collection of the same type containing those elements, mapped and flattened. If no collection is supplied, a
* transducer function, suitable for passing to `{@link module:xduce.sequence|sequence}`,
* `{@link module:xduce.into|into}`, etc. is returned.
*/
function flatMap(collection, fn, ctx) {
const [col, func] = isFunction(collection) ? [null, collection.bind(fn)] : [collection, fn.bind(ctx)];
return col ? sequence(col, compose(map(func), flatten())) : compose(map(func), flatten());
}
module.exports = {
map,
flatMap
};