Repository: remeda/remeda
Pull request #577 (2024-03-19 13:41:57)

feat: add `sum` and `product`
These basic functions make sense.

---

Make sure that you:

- [x] Typedoc added for new methods and updated for changed
- [x] Tests added for new methods and updated for changed
- [x] New methods added to `src/index.ts`
- [x] New methods added to `mapping.md`

---

<details><summary>We use semantic PR titles to automate the release process!</summary>

https://conventionalcommits.org

PRs should be titled following using the format: `< TYPE >(< scope >)?: description`

### Available Types:

- `feat`: new functions, and changes to a function's type that would impact users.
- `fix`: changes to the runtime behavior of an existing function, or refinements to it's type that shouldn't impact most users.
- `perf`: changes to function implementations that improve a functions _runtime_ performance.
- `refactor`: changes to function implementations that are neither `fix` nor `perf`
- `test`: tests-only changes (transparent to users of the function).
- `docs`: changes to the documentation of a function **or the documentation site**.
- `build`, `ci`, `style`, `chore`, and `revert`: are only relevant for the internals of the library.

For scope put the name of the function you are working on (either new or
existing).

</details>



Patch:
diff --git a/mapping.md b/mapping.md
index b849f8c..16b344a 100644
--- a/mapping.md
+++ b/mapping.md
@@ -74,6 +74,7 @@ documentation when migrating._
 | [`pick`](https://remedajs.com/docs/#pick)                           | [`pick`](https://lodash.com/docs/4.17.15#pick)                           | [`pick`](https://ramdajs.com/docs/#pick)                           |
 | [`pickBy`](https://remedajs.com/docs/#pickBy)                       | [`pickBy`](https://lodash.com/docs/4.17.15#pickBy)                       | [`pickBy`](https://ramdajs.com/docs/#pickBy)                       |
 | [`pipe`](https://remedajs.com/docs/#pipe)                           | [`flow`](https://lodash.com/docs/4.17.15#flow)                           | [`pipe`](https://ramdajs.com/docs/#pipe)                           |
+| [`product`](https://remedajs.com/docs/#product)                     |                                                                          | [`product`](https://ramdajs.com/docs/#product)                     |
 | [`prop`](https://remedajs.com/docs/#prop)                           | [`get`](https://lodash.com/docs/4.17.15#get)                             | [`prop`](https://ramdajs.com/docs/#prop)                           |
 | [`randomString`](https://remedajs.com/docs/#randomString)           |                                                                          |                                                                    |
 | [`range`](https://remedajs.com/docs/#range)                         | [`range`](https://lodash.com/docs/4.17.15#range)                         | [`range`](https://ramdajs.com/docs/#range)                         |
@@ -96,6 +97,7 @@ documentation when migrating._
 | [`splitWhen`](https://remedajs.com/docs/#splitWhen)                 |                                                                          | [`splitWhen`](https://ramdajs.com/docs/#splitWhen)                 |
 | [`stringToPath`](https://remedajs.com/docs/#stringToPath)           | [`toPath`](https://lodash.com/docs/4.17.15#toPath)                       |                                                                    |
 | [`subtract`](https://remedajs.com/docs/#subtract)                   | [`subtract`](https://lodash.com/docs/4.17.15#subtract)                   | [`subtract`](https://ramdajs.com/docs/#subtract)                   |
+| [`sum`](https://remedajs.com/docs/#sum)                             | [`sum`](https://lodash.com/docs/4.17.15#sum)                             | [`sum`](https://ramdajs.com/docs/#sum)                             |
 | [`sumBy`](https://remedajs.com/docs/#sumBy)                         | [`sumBy`](https://lodash.com/docs/4.17.15#sumBy)                         |                                                                    |
 | [`swapIndices`](https://remedajs.com/docs/#swapIndices)             |                                                                          | [`swap`](https://ramdajs.com/docs/#swap)                           |
 | [`swapProps`](https://remedajs.com/docs/#swapProps)                 |                                                                          | [`swap`](https://ramdajs.com/docs/#swap)                           |
diff --git a/src/index.ts b/src/index.ts
index e1469b4..ea21786 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -101,6 +101,7 @@ export * from "./pick";
 export * from "./pickBy";
 export * from "./pipe";
 export * from "./piped";
+export * from "./product";
 export * from "./prop";
 export * from "./purry";
 export * from "./randomString";
@@ -127,6 +128,7 @@ export * from "./splitAt";
 export * from "./splitWhen";
 export * from "./stringToPath";
 export * from "./subtract";
+export * from "./sum";
 export * from "./sumBy";
 export * from "./swapIndices";
 export * from "./swapProps";
diff --git a/src/product.ts b/src/product.ts
new file mode 100644
index 0000000..65099cd
--- /dev/null
+++ b/src/product.ts
@@ -0,0 +1,44 @@
+import { purry } from "./purry";
+
+// TODO: Support bigint once we bump our typescript version beyond ES5
+
+/**
+ * Compute the product of the numbers in the array, or return 1 for an empty
+ * array.
+ *
+ * @param data - The array of numbers.
+ * @signature
+ *   R.product(data);
+ * @example
+ *   R.product([1, 2, 3]); // => 6
+ *   R.product([]); // => 1
+ * @dataFirst
+ * @category Number
+ */
+export function product(data: ReadonlyArray<number>): number;
+
+/**
+ * Compute the product of the numbers in the array, or return 1 for an empty
+ * array.
+ *
+ * @signature
+ *   R.product()(data);
+ * @example
+ *   R.pipe([1, 2, 3], R.product()); // => 6
+ *   R.pipe([], R.product()); // => 0
+ * @dataLast
+ * @category Number
+ */
+export function product(): (data: ReadonlyArray<number>) => number;
+
+export function product(): unknown {
+  return purry(productImplementation, arguments);
+}
+
+function productImplementation(data: ReadonlyArray<number>): number {
+  let out = 1;
+  for (const value of data) {
+    out *= value;
+  }
+  return out;
+}
diff --git a/src/sum.ts b/src/sum.ts
new file mode 100644
index 0000000..f01a9bf
--- /dev/null
+++ b/src/sum.ts
@@ -0,0 +1,42 @@
+import { purry } from "./purry";
+
+// TODO: Support bigint once we bump our typescript version beyond ES5
+
+/**
+ * Sums the numbers in the array, or return 0 for an empty array.
+ *
+ * @param data - The array of numbers.
+ * @signature
+ *   R.sum(data);
+ * @example
+ *   R.sum([1, 2, 3]); // => 6
+ *   R.sum([]); // => 0
+ * @dataFirst
+ * @category Number
+ */
+export function sum(data: ReadonlyArray<number>): number;
+
+/**
+ * Sums the numbers in the array, or return 0 for an empty array.
+ *
+ * @signature
+ *   R.sum()(data);
+ * @example
+ *   R.pipe([1, 2, 3], R.sum()); // => 6
+ *   R.pipe([], R.sum()); // => 0
+ * @dataLast
+ * @category Number
+ */
+export function sum(): (data: ReadonlyArray<number>) => number;
+
+export function sum(): unknown {
+  return purry(sumImplementation, arguments);
+}
+
+function sumImplementation(data: ReadonlyArray<number>): number {
+  let out = 0;
+  for (const value of data) {
+    out += value;
+  }
+  return out;
+}


Test patch:
diff --git a/src/product.test.ts b/src/product.test.ts
new file mode 100644
index 0000000..8c56768
--- /dev/null
+++ b/src/product.test.ts
@@ -0,0 +1,30 @@
+import { pipe } from "./pipe";
+import { product } from "./product";
+
+describe("runtime", () => {
+  describe("dataFirst", () => {
+    it("should return the product of numbers in the array", () => {
+      expect(product([1, 2, 3])).toBe(6);
+      expect(product([4, 5, 6])).toBe(120);
+      expect(product([0, 1, 2])).toBe(0);
+      expect(product([-1, -2, -3])).toBe(-6);
+    });
+
+    it("should return 1 for an empty array", () => {
+      expect(product([])).toBe(1);
+    });
+  });
+
+  describe("dataLast", () => {
+    it("should return the product of numbers in the array", () => {
+      expect(pipe([1, 2, 3], product())).toBe(6);
+      expect(pipe([4, 5, 6], product())).toBe(120);
+      expect(pipe([0, 1, 2], product())).toBe(0);
+      expect(pipe([-1, -2, -3], product())).toBe(-6);
+    });
+
+    it("should return 1 for an empty array", () => {
+      expect(pipe([], product())).toBe(1);
+    });
+  });
+});
diff --git a/src/sum.test.ts b/src/sum.test.ts
new file mode 100644
index 0000000..e36ad8b
--- /dev/null
+++ b/src/sum.test.ts
@@ -0,0 +1,28 @@
+import { pipe } from "./pipe";
+import { sum } from "./sum";
+
+describe("runtime", () => {
+  describe("dataFirst", () => {
+    it("should return the sum of numbers in an array", () => {
+      expect(sum([1, 2, 3])).toBe(6);
+      expect(sum([4, 5, 6])).toBe(15);
+      expect(sum([-1, 0, 1])).toBe(0);
+    });
+
+    it("should return 0 for an empty array", () => {
+      expect(sum([])).toBe(0);
+    });
+  });
+
+  describe("dataLast", () => {
+    it("should return the sum of numbers in an array", () => {
+      expect(pipe([1, 2, 3], sum())).toBe(6);
+      expect(pipe([4, 5, 6], sum())).toBe(15);
+      expect(pipe([-1, 0, 1], sum())).toBe(0);
+    });
+
+    it("should return 0 for an empty array", () => {
+      expect(pipe([], sum())).toBe(0);
+    });
+  });
+});