diff --git a/src/concatAll.js b/src/concatAll.js new file mode 100644 index 0000000000..017c465dd0 --- /dev/null +++ b/src/concatAll.js @@ -0,0 +1,37 @@ +import { concat, identical, identity, pipe, when, reduce } from 'ramda'; + +import stubUndefined from './stubUndefined'; + +const leftIdentitySemigroup = { concat: identity }; + +/** + * Returns the result of concatenating the given lists or strings. + * Note: RA.concatAll expects all elements to be of the same type. It will throw an error if you concat an Array with a non-Array value. + * Dispatches to the concat method of the preceding element, if present. Can also concatenate multiple elements of a [fantasy-land compatible semigroup](https://github.com/fantasyland/fantasy-land#semigroup). + * Returns undefined if empty array was passed. + * + * @func concatAll + * @memberOf RA + * @since {@link https://char0n.github.io/ramda-adjunct/2.6.0|v2.6.0} + * @category List + * @sig [[a]] -> [a] | Undefined + * @sig [String] -> String | Undefined + * @sig Semigroup s => Foldable s f => f -> s | Undefined + * @param {Array.} list List containing elements that will be concatenated + * @return {Array|string|undefined} Concatenated elements + * @see {@link http://ramdajs.com/docs/#concat|concat} + * @see {@link RA.concatRight|concatRight} + * @see {@link http://ramdajs.com/docs/#unnest|unnest} + * @see {@link http://ramdajs.com/docs/#join|join} + * @example + * + * concatAll([[1], [2], [3]]); //=> [1, 2, 3] + * concatAll(['1', '2', '3']); //=> '123' + * concatAll([]); //=> undefined; + */ +const concatAll = pipe( + reduce(concat, leftIdentitySemigroup), + when(identical(leftIdentitySemigroup), stubUndefined) +); + +export default concatAll; diff --git a/src/index.d.ts b/src/index.d.ts index c1b3338d35..67d1d99f8f 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -10,6 +10,15 @@ declare namespace RamdaAdjunct { ap(fn: Apply<(t: T) => U>): Apply; } + interface Foldable { + reduce(fn: (acc: Acc, val: T) => Acc, initAcc: Acc): Acc; + } + + interface Semigroup { + // https://www.typescriptlang.org/docs/handbook/advanced-types.html#polymorphic-this-types + concat(other: this): this; + } + interface Catamorphism { cata(leftFn: (v: T1) => T, rightFn: (v: T1) => T): T; } @@ -376,6 +385,14 @@ declare namespace RamdaAdjunct { */ ensureArray(value: T | T[]): T[]; + /** + * Returns the result of concatenating the given lists or strings. + * Note: RA.concatAll expects all elements to be of the same type. It will throw an error if you concat an Array with a non-Array value. + * Dispatches to the concat method of the preceding element, if present. Can also concatenate multiple elements of a [fantasy-land compatible semigroup](https://github.com/fantasyland/fantasy-land#semigroup). + * Returns undefined if empty array was passed. + */ + concatAll(foldable: Foldable): S | undefined; + /** * Returns the result of concatenating the given lists or strings. */ diff --git a/src/index.js b/src/index.js index 4e1b49ee2f..7445d9cafa 100644 --- a/src/index.js +++ b/src/index.js @@ -95,6 +95,7 @@ export { default as reduceIndexed } from './reduceIndexed'; export { default as pickIndexes } from './pickIndexes'; export { default as list } from './list'; export { default as ensureArray } from './ensureArray'; +export { default as concatAll } from './concatAll'; export { default as concatRight } from './concatRight'; export { default as reduceP } from './reduceP'; export { default as reduceRightP } from './reduceRightP'; diff --git a/test/concatAll.js b/test/concatAll.js new file mode 100644 index 0000000000..7ca1a08f87 --- /dev/null +++ b/test/concatAll.js @@ -0,0 +1,46 @@ +import { assert } from 'chai'; +import { NEL, Nil } from 'monet'; + +import * as RA from '../src'; +import eq from './shared/eq'; + +describe('concatAll', function() { + it('should concatenate arrays', function() { + eq(RA.concatAll([[1], [2], [3]]), [1, 2, 3]); + }); + + it('should concatenate strings', function() { + eq(RA.concatAll(['1', '2', '3']), '123'); + }); + + it('should concatenate semigroups', function() { + eq(RA.concatAll([NEL(1), NEL(2)]), NEL(1, NEL(2, Nil))); + }); + + context('when foldable is empty', function() { + specify('should returns undefined', function() { + eq(RA.concatAll([]), undefined); + }); + }); + + context('when foldable contains non-semigroups', function() { + specify('should throw', function() { + assert.throws(() => RA.concatAll([1, 2, null, true]), TypeError); + }); + }); + + /* + // fails on Ramda 0.21 + context('when foldable contains non-compatible semigroups', function() { + specify('should throw', function() { + assert.throws(() => RA.concatAll([[1], '1']), TypeError); + }); + }); + */ + + context('when passed non-foldable', function() { + specify('should throw', function() { + assert.throws(() => RA.concatAll(null), TypeError); + }); + }); +});