Repository: remeda/remeda
Pull request #585 (2024-03-22 10:11:13)

feat: remove indexed variants
Removing indexed variant and incorporating the indexed callback variant as the main one

codesandbox-ci[bot]: This pull request is automatically built and testable in [CodeSandbox](https://codesandbox.io).

  To see build info of the built libraries, click [here](https://ci.codesandbox.io/status/remeda/remeda/pr/585/builds/486555) or the icon next to each commit SHA.

Latest deployment of this branch, based on commit c2617a9310da9a7d7053e3713596cf1637f6c03a:

|Sandbox| Source |
|--|--|
|[remeda-example-vanilla](https://codesandbox.io/s/remeda-example-vanilla-86qkhw)| Configuration |


Patch:
diff --git a/README.md b/README.md
index 3b9027e..79d56c0 100644
--- a/README.md
+++ b/README.md
@@ -123,20 +123,6 @@ const result = R.pipe(
 
 ```
 
-## Indexed version
-
-Iterable functions have an extra property `indexed` which is the same function with iterator `(element, index, array)`.
-
-```js
-const arr = [10, 12, 13, 3];
-
-// filter even values
-R.filter(arr, (x) => x % 2 === 0); // => [10, 12]
-
-// filter even indexes
-R.filter.indexed(arr, (x, i) => i % 2 === 0); // => [10, 13]
-```
-
 ## For Lodash and Ramda users
 
 Please check function mapping in [mapping.md](./mapping.md).
diff --git a/docs/src/components/function-tag.tsx b/docs/src/components/function-tag.tsx
index 1578d8e..dca9890 100644
--- a/docs/src/components/function-tag.tsx
+++ b/docs/src/components/function-tag.tsx
@@ -3,7 +3,6 @@ import { cn } from "@/lib/utils";
 
 const tagToClass: Record<string, string> = {
   pipeable: "bg-green-600 text-green-50 dark:bg-green-500 dark:text-green-950",
-  indexed: "bg-sky-600 text-sky-50 dark:bg-sky-500 dark:text-sky-950",
 };
 
 interface Props {
diff --git a/docs/src/lib/get-tags.ts b/docs/src/lib/get-tags.ts
index a78a4d6..083e1cf 100644
--- a/docs/src/lib/get-tags.ts
+++ b/docs/src/lib/get-tags.ts
@@ -3,7 +3,7 @@ import type { DocumentedFunction } from "./transform";
 export function getTags({
   methods: [method],
 }: DocumentedFunction): ReadonlyArray<string> {
-  const { pipeable = false, indexed = false } = method ?? {};
+  const { pipeable = false } = method ?? {};
 
   const out = [];
 
@@ -11,9 +11,5 @@ export function getTags({
     out.push("pipeable");
   }
 
-  if (indexed) {
-    out.push("indexed");
-  }
-
   return out;
 }
diff --git a/docs/src/lib/transform.ts b/docs/src/lib/transform.ts
index 31f9123..2f43c72 100644
--- a/docs/src/lib/transform.ts
+++ b/docs/src/lib/transform.ts
@@ -78,7 +78,6 @@ function transformSignature({
   return {
     tag: getFunctionCurriedVariant(comment),
     signature: tagContent(comment, "signature"),
-    indexed: hasTag(comment, "indexed"),
     pipeable: hasTag(comment, "pipeable"),
     example: tagContent(comment, "example"),
     args: parameters.map(getParameter),
diff --git a/docs/src/pages/index.astro b/docs/src/pages/index.astro
index 6c6885b..4123138 100644
--- a/docs/src/pages/index.astro
+++ b/docs/src/pages/index.astro
@@ -162,27 +162,6 @@ const result = R.pipe(
        `
         />
       </section>
-
-      <section>
-        <Typography.H2>Indexed version</Typography.H2>
-
-        <Typography.P>
-          Iterable functions have an extra property <code>indexed</code> which is
-          the same function with iterator <code>(element, index, array)</code>.
-        </Typography.P>
-
-        <CodeBlock
-          code=`
-const arr = [10, 12, 13, 3];
-
-// filter even values
-R.filter(arr, x => x % 2 === 0); // => [10, 12]
-
-// filter even indexes
-R.filter.indexed(arr, (x, i) => i % 2 === 0); // => [10, 13]
-      `
-        />
-      </section>
     </div>
   </main>
 </Layout>
diff --git a/eslint.config.js b/eslint.config.js
index b925020..8e4a0a5 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -33,6 +33,11 @@ export default config(
       // etc...) via namespaces by design.
       "@typescript-eslint/no-namespace": "off",
 
+      // Whenever we call a built-in function we want to be as transparent as
+      // possible so we pass the callback directly without wrapping it with an
+      // arrow function. Our typing provides the safety needed here.
+      "unicorn/no-array-callback-reference": "off",
+
       // @see https://tkdodo.eu/blog/array-types-in-type-script
       "@typescript-eslint/array-type": ["error", { default: "generic" }],
 
diff --git a/src/_binarySearchCutoffIndex.ts b/src/_binarySearchCutoffIndex.ts
index c47ada2..5262d65 100644
--- a/src/_binarySearchCutoffIndex.ts
+++ b/src/_binarySearchCutoffIndex.ts
@@ -1,6 +1,6 @@
 export function _binarySearchCutoffIndex<T>(
   array: ReadonlyArray<T>,
-  predicate: (item: T, index: number) => boolean,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
 ): number {
   // !IMPORTANT: This docblock is not above the function because then the code that builds our docs site breaks. Please do not move it.
   /**
@@ -27,7 +27,7 @@ export function _binarySearchCutoffIndex<T>(
     const pivotIndex = (lowIndex + highIndex) >>> 1;
     const pivot = array[pivotIndex]!;
 
-    if (predicate(pivot, pivotIndex)) {
+    if (predicate(pivot, pivotIndex, array)) {
       lowIndex = pivotIndex + 1;
     } else {
       highIndex = pivotIndex;
diff --git a/src/_reduceLazy.ts b/src/_reduceLazy.ts
index 69a687b..458c763 100644
--- a/src/_reduceLazy.ts
+++ b/src/_reduceLazy.ts
@@ -3,14 +3,13 @@ import type { LazyEvaluator } from "./pipe";
 export function _reduceLazy<T, K>(
   array: ReadonlyArray<T>,
   lazy: LazyEvaluator<T, K>,
-  isIndexed = false,
 ): Array<K> {
   const out: Array<K> = [];
 
   // We intentionally use a for loop here instead of reduce for performance reasons. See https://leanylabs.com/blog/js-forEach-map-reduce-vs-for-for_of/ for more info
   for (let index = 0; index < array.length; index++) {
     const item = array[index]!;
-    const result = isIndexed ? lazy(item, index, array) : lazy(item);
+    const result = lazy(item, index, array);
     if (result.hasMany === true) {
       out.push(...result.next);
     } else if (result.hasNext) {
diff --git a/src/_toLazyIndexed.ts b/src/_toLazyIndexed.ts
deleted file mode 100644
index f0d99c7..0000000
--- a/src/_toLazyIndexed.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Typescript requires using `any` to infer any kind of function type, `unknown` is not enough.
-export const _toLazyIndexed = <Func extends (...args: any) => unknown>(
-  fn: Func,
-): Func & { readonly indexed: true } =>
-  Object.assign(fn, { indexed: true as const });
diff --git a/src/_types.ts b/src/_types.ts
index 012a509..5b7d9ad 100644
--- a/src/_types.ts
+++ b/src/_types.ts
@@ -1,17 +1,5 @@
 import type { IsAny } from "./type-fest/is-any";
 
-export type Pred<T, K> = (input: T) => K;
-export type PredIndexed<T, K> = (
-  input: T,
-  index: number,
-  array: ReadonlyArray<T>,
-) => K;
-export type PredIndexedOptional<T, K> = (
-  input: T,
-  index?: number,
-  array?: ReadonlyArray<T>,
-) => K;
-
 export type NonEmptyArray<T> = [T, ...Array<T>];
 
 export type Mapped<T extends IterableContainer, K> = {
diff --git a/src/filter.ts b/src/filter.ts
index 862ee15..b1a8134 100644
--- a/src/filter.ts
+++ b/src/filter.ts
@@ -1,96 +1,73 @@
-import { _reduceLazy } from "./_reduceLazy";
-import { _toLazyIndexed } from "./_toLazyIndexed";
-import type { Pred, PredIndexed, PredIndexedOptional } from "./_types";
 import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
 /**
- * Filter the elements of an array that meet the condition specified in a callback function.
+ * Creates a shallow copy of a portion of a given array, filtered down to just
+ * the elements from the given array that pass the test implemented by the
+ * provided function. Equivalent to `Array.prototype.filter`.
  *
- * @param array - The array to filter.
- * @param fn - The callback function.
+ * @param data - The array to filter.
+ * @param predicate - A function to execute for each element in the array. It
+ * should return `true` to keep the element in the resulting array, and `false`
+ * otherwise. A type-predicate can also be used to narrow the result.
+ * @returns A shallow copy of the given array containing just the elements that
+ * pass the test. If no elements pass the test, an empty array is returned.
  * @signature
- *    R.filter(array, fn)
- *    R.filter.indexed(array, fn)
+ *    R.filter(data, predicate)
  * @example
  *    R.filter([1, 2, 3], x => x % 2 === 1) // => [1, 3]
- *    R.filter.indexed([1, 2, 3], (x, i, array) => x % 2 === 1) // => [1, 3]
  * @dataFirst
- * @indexed
  * @pipeable
  * @category Array
  */
 export function filter<T, S extends T>(
-  array: ReadonlyArray<T>,
-  fn: (value: T) => value is S,
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
 ): Array<S>;
 export function filter<T>(
-  array: ReadonlyArray<T>,
-  fn: Pred<T, boolean>,
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
 ): Array<T>;
 
 /**
- * Filter the elements of an array that meet the condition specified in a callback function.
+ * Creates a shallow copy of a portion of a given array, filtered down to just
+ * the elements from the given array that pass the test implemented by the
+ * provided function. Equivalent to `Array.prototype.filter`.
  *
- * @param fn - The callback function.
+ * @param predicate - A function to execute for each element in the array. It
+ * should return `true` to keep the element in the resulting array, and `false`
+ * otherwise.
+ * @returns A shallow copy of the given array containing just the elements that
+ * pass the test. If no elements pass the test, an empty array is returned.
  * @signature
- *    R.filter(fn)(array)
- *    R.filter.indexed(fn)(array)
+ *    R.filter(predicate)(data)
  * @example
  *    R.pipe([1, 2, 3], R.filter(x => x % 2 === 1)) // => [1, 3]
- *    R.pipe([1, 2, 3], R.filter.indexed((x, i) => x % 2 === 1)) // => [1, 3]
  * @dataLast
- * @indexed
  * @pipeable
  * @category Array
  */
 export function filter<T, S extends T>(
-  fn: (input: T) => input is S,
-): (array: ReadonlyArray<T>) => Array<S>;
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
+): (data: ReadonlyArray<T>) => Array<S>;
 export function filter<T>(
-  fn: Pred<T, boolean>,
-): (array: ReadonlyArray<T>) => Array<T>;
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
+): (data: ReadonlyArray<T>) => Array<T>;
 
 export function filter(): unknown {
-  return purry(_filter(false), arguments, filter.lazy);
+  return purry(filterImplementation, arguments, lazyImplementation);
 }
 
-const _filter =
-  (indexed: boolean) =>
-  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, boolean>) =>
-    _reduceLazy(
-      array,
-      indexed ? filter.lazyIndexed(fn) : filter.lazy(fn),
-      indexed,
-    );
+const filterImplementation = <T>(
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, array: ReadonlyArray<T>) => boolean,
+): Array<T> => data.filter(predicate);
 
-const _lazy =
-  (indexed: boolean) =>
-  <T>(fn: PredIndexedOptional<T, boolean>): LazyEvaluator<T> =>
-  (value, index, array) =>
-    (indexed ? fn(value, index, array) : fn(value))
+const lazyImplementation =
+  <T>(
+    predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
+  ): LazyEvaluator<T> =>
+  (value, index, data) =>
+    predicate(value, index, data)
       ? { done: false, hasNext: true, next: value }
       : { done: false, hasNext: false };
-
-export namespace filter {
-  export function indexed<T, S extends T>(
-    array: ReadonlyArray<T>,
-    fn: (input: T, index: number, array: ReadonlyArray<T>) => input is S,
-  ): Array<S>;
-  export function indexed<T>(
-    array: ReadonlyArray<T>,
-    fn: PredIndexed<T, boolean>,
-  ): Array<T>;
-  export function indexed<T, S extends T>(
-    fn: (input: T, index: number, array: ReadonlyArray<T>) => input is S,
-  ): (array: ReadonlyArray<T>) => Array<S>;
-  export function indexed<T>(
-    fn: PredIndexed<T, boolean>,
-  ): (array: ReadonlyArray<T>) => Array<T>;
-  export function indexed(): unknown {
-    return purry(_filter(true), arguments, filter.lazyIndexed);
-  }
-
-  export const lazy = _lazy(false);
-  export const lazyIndexed = _toLazyIndexed(_lazy(true));
-}
diff --git a/src/find.ts b/src/find.ts
index 84ff279..a43c7d3 100644
--- a/src/find.ts
+++ b/src/find.ts
@@ -1,87 +1,93 @@
-import { _toLazyIndexed } from "./_toLazyIndexed";
 import { _toSingle } from "./_toSingle";
-import type { Pred, PredIndexed, PredIndexedOptional } from "./_types";
 import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
 /**
- * Returns the value of the first element in the array where predicate is true, and undefined otherwise.
+ * Returns the first element in the provided array that satisfies the provided
+ * testing function. If no values satisfy the testing function, `undefined` is
+ * returned.
  *
- * @param array - The array.
- * @param fn - The predicate.
+ * Similar functions:
+ * * `findLast` - If you need the last element that satisfies the provided testing function.
+ * * `findIndex` - If you need the index of the found element in the array.
+ * * `indexOf` - If you need to find the index of a value.
+ * * `includes` - If you need to find if a value exists in an array.
+ * * `some` - If you need to find if any element satisfies the provided testing function.
+ * * `filter` - If you need to find all elements that satisfy the provided testing function.
+ *
+ * @param data - The items to search in.
+ * @param predicate - A function to execute for each element in the array. It
+ * should return `true` to indicate a matching element has been found, and
+ * `false` otherwise. A type-predicate can also be used to narrow the result.
+ * @returns The first element in the array that satisfies the provided testing
+ * function. Otherwise, `undefined` is returned.
  * @signature
- *    R.find(items, fn)
- *    R.find.indexed(items, fn)
+ *    R.find(data, predicate)
  * @example
  *    R.find([1, 3, 4, 6], n => n % 2 === 0) // => 4
- *    R.find.indexed([1, 3, 4, 6], (n, i) => n % 2 === 0) // => 4
  * @dataFirst
- * @indexed
  * @pipeable
  * @category Array
  */
+export function find<T, S extends T>(
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
+): S | undefined;
 export function find<T>(
-  array: ReadonlyArray<T>,
-  fn: Pred<T, boolean>,
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
 ): T | undefined;
 
 /**
- * Returns the value of the first element in the array where predicate is true, and undefined otherwise.
+ * Returns the first element in the provided array that satisfies the provided
+ * testing function. If no values satisfy the testing function, `undefined` is
+ * returned.
+ *
+ * Similar functions:
+ * * `findLast` - If you need the last element that satisfies the provided testing function.
+ * * `findIndex` - If you need the index of the found element in the array.
+ * * `indexOf` - If you need to find the index of a value.
+ * * `includes` - If you need to find if a value exists in an array.
+ * * `some` - If you need to find if any element satisfies the provided testing function.
+ * * `filter` - If you need to find all elements that satisfy the provided testing function.
  *
- * @param fn - The predicate.
+ * @param predicate - A function to execute for each element in the array. It
+ * should return `true` to indicate a matching element has been found, and
+ * `false` otherwise. A type-predicate can also be used to narrow the result.
+ * @returns The first element in the array that satisfies the provided testing
+ * function. Otherwise, `undefined` is returned.
  * @signature
- *    R.find(fn)(items)
- *    R.find.indexed(fn)(items)
+ *    R.find(predicate)(data)
  * @example
  *    R.pipe(
  *      [1, 3, 4, 6],
  *      R.find(n => n % 2 === 0)
  *    ) // => 4
- *    R.pipe(
- *      [1, 3, 4, 6],
- *      R.find.indexed((n, i) => n % 2 === 0)
- *    ) // => 4
  * @dataLast
- * @indexed
  * @pipeable
  * @category Array
  */
-export function find<T = never>(
-  fn: Pred<T, boolean>,
-): (array: ReadonlyArray<T>) => T | undefined;
+export function find<T, S extends T>(
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
+): (data: ReadonlyArray<T>) => S | undefined;
+export function find<T>(
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
+): (data: ReadonlyArray<T>) => T | undefined;
 
 export function find(): unknown {
-  return purry(_find(false), arguments, find.lazy);
+  return purry(findImplementation, arguments, _toSingle(lazyImplementation));
 }
 
-const _find =
-  (indexed: boolean) =>
-  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, boolean>) =>
-    array.find((item, index, input) =>
-      indexed ? fn(item, index, input) : fn(item),
-    );
+const findImplementation = <T, S extends T>(
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
+): S | undefined => data.find(predicate);
 
-const _lazy =
-  (indexed: boolean) =>
-  <T>(fn: PredIndexedOptional<T, boolean>): LazyEvaluator<T> =>
-  (value, index, array) =>
-    (indexed ? fn(value, index, array) : fn(value))
+const lazyImplementation =
+  <T, S extends T>(
+    predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
+  ): LazyEvaluator<T, S> =>
+  (value, index, data) =>
+    predicate(value, index, data)
       ? { done: true, hasNext: true, next: value }
       : { done: false, hasNext: false };
-
-export namespace find {
-  export function indexed<T>(
-    array: ReadonlyArray<T>,
-    fn: PredIndexed<T, boolean>,
-  ): T | undefined;
-  export function indexed<T>(
-    fn: PredIndexed<T, boolean>,
-  ): (array: ReadonlyArray<T>) => T | undefined;
-  export function indexed(): unknown {
-    return purry(_find(true), arguments, find.lazyIndexed);
-  }
-
-  export const lazy = _toSingle(_lazy(false));
-
-  export const lazyIndexed = _toSingle(_toLazyIndexed(_lazy(true)));
-}
diff --git a/src/findIndex.ts b/src/findIndex.ts
index 93b6335..bf0f852 100644
--- a/src/findIndex.ts
+++ b/src/findIndex.ts
@@ -1,93 +1,63 @@
-import { _toLazyIndexed } from "./_toLazyIndexed";
-import { _toSingle } from "./_toSingle";
-import type { Pred, PredIndexed, PredIndexedOptional } from "./_types";
-import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
 /**
- * Returns the index of the first element in the array where predicate is true, and -1 otherwise.
+ * Returns the index of the first element in an array that satisfies the
+ * provided testing function. If no elements satisfy the testing function, -1 is
+ * returned.
  *
- * @param array - The array.
- * @param fn - The predicate.
+ * See also the `find` method, which returns the first element that satisfies
+ * the testing function (rather than its index).
+ *
+ * @param data - The items to search in.
+ * @param predicate - A function to execute for each element in the array. It
+ * should return a `true` to indicate a matching element has been found, and a
+ * `false` otherwise.
+ * @returns The index of the first element in the array that passes the test.
+ * Otherwise, -1.
  * @signature
- *    R.findIndex(items, fn)
- *    R.findIndex.indexed(items, fn)
+ *    R.findIndex(data, predicate)
  * @example
  *    R.findIndex([1, 3, 4, 6], n => n % 2 === 0) // => 2
- *    R.findIndex.indexed([1, 3, 4, 6], (n, i) => n % 2 === 0) // => 2
  * @dataFirst
- * @indexed
- * @pipeable
  * @category Array
  */
 export function findIndex<T>(
-  array: ReadonlyArray<T>,
-  fn: Pred<T, boolean>,
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, obj: ReadonlyArray<T>) => boolean,
 ): number;
 
 /**
- * Returns the index of the first element in the array where predicate is true, and -1 otherwise.
+ * Returns the index of the first element in an array that satisfies the
+ * provided testing function. If no elements satisfy the testing function, -1 is
+ * returned.
+ *
+ * See also the `find` method, which returns the first element that satisfies
+ * the testing function (rather than its index).
  *
- * @param fn - The predicate.
+ * @param predicate - A function to execute for each element in the array. It
+ * should return a `true` to indicate a matching element has been found, and a
+ * `false` otherwise.
+ * @returns The index of the first element in the array that passes the test.
+ * Otherwise, -1.
  * @signature
- *    R.findIndex(fn)(items)
- *    R.findIndex.indexed(fn)(items)
+ *    R.findIndex(predicate)(data)
  * @example
  *    R.pipe(
  *      [1, 3, 4, 6],
  *      R.findIndex(n => n % 2 === 0)
- *    ) // => 2
- *    R.pipe(
- *      [1, 3, 4, 6],
- *      R.findIndex.indexed((n, i) => n % 2 === 0)
- *    ) // => 2
+ *    ); // => 2
  * @dataLast
- * @indexed
- * @pipeable
  * @category Array
  */
 export function findIndex<T>(
-  fn: Pred<T, boolean>,
-): (array: ReadonlyArray<T>) => number;
+  predicate: (value: T, index: number, obj: ReadonlyArray<T>) => boolean,
+): (data: ReadonlyArray<T>) => number;
 
 export function findIndex(): unknown {
-  return purry(_findIndex(false), arguments, findIndex.lazy);
+  return purry(findIndexImplementation, arguments);
 }
 
-const _findIndex =
-  (indexed: boolean) =>
-  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, boolean>) =>
-    array.findIndex((item, index, input) =>
-      indexed ? fn(item, index, input) : fn(item),
-    );
-
-const _lazy =
-  (indexed: boolean) =>
-  <T>(fn: PredIndexedOptional<T, boolean>): LazyEvaluator<T, number> => {
-    // TODO: We use the `actualIndex` here because we can't trust the index coming from pipe. This is due to the fact that the `indexed` abstraction might turn off incrementing the index or not send it at all. Once we simplify the code base by removing the non-indexed versions, we can remove this.
-    let actualIndex = 0;
-    return (value, index, array) => {
-      if (indexed ? fn(value, index, array) : fn(value)) {
-        return { done: true, hasNext: true, next: actualIndex };
-      }
-      actualIndex += 1;
-      return { done: false, hasNext: false };
-    };
-  };
-
-export namespace findIndex {
-  export function indexed<T>(
-    array: ReadonlyArray<T>,
-    fn: PredIndexed<T, boolean>,
-  ): number;
-  export function indexed<T>(
-    fn: PredIndexed<T, boolean>,
-  ): (array: ReadonlyArray<T>) => number;
-  export function indexed(): unknown {
-    return purry(_findIndex(true), arguments, findIndex.lazyIndexed);
-  }
-
-  export const lazy = _toSingle(_lazy(false));
-
-  export const lazyIndexed = _toSingle(_toLazyIndexed(_lazy(true)));
-}
+const findIndexImplementation = <T>(
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, obj: ReadonlyArray<T>) => boolean,
+): number => data.findIndex(predicate);
diff --git a/src/findLast.ts b/src/findLast.ts
index dfd61d3..3d692ff 100644
--- a/src/findLast.ts
+++ b/src/findLast.ts
@@ -1,79 +1,89 @@
-import type { Pred, PredIndexed, PredIndexedOptional } from "./_types";
 import { purry } from "./purry";
 
 /**
- * Returns the value of the last element in the array where predicate is true, and undefined
- * otherwise.
+ * Iterates the array in reverse order and returns the value of the first
+ * element that satisfies the provided testing function. If no elements satisfy
+ * the testing function, undefined is returned.
  *
- * @param array - The array.
- * @param fn - The predicate.
+ * Similar functions:
+ * * `find` - If you need the first element that satisfies the provided testing function.
+ * * `findLastIndex` - If you need the index of the found element in the array.
+ * * `lastIndexOf` - If you need to find the index of a value.
+ * * `includes` - If you need to find if a value exists in an array.
+ * * `some` - If you need to find if any element satisfies the provided testing function.
+ * * `filter` - If you need to find all elements that satisfy the provided testing function.
+ *
+ * @param data - The items to search in.
+ * @param predicate - A function to execute for each element in the array. It
+ * should return `true` to indicate a matching element has been found, and
+ * `false` otherwise. A type-predicate can also be used to narrow the result.
+ * @returns The last (highest-index) element in the array that satisfies the
+ * provided testing function; undefined if no matching element is found.
  * @signature
- *    R.findLast(items, fn)
- *    R.findLast.indexed(items, fn)
+ *    R.findLast(data, predicate)
  * @example
  *    R.findLast([1, 3, 4, 6], n => n % 2 === 1) // => 3
- *    R.findLast.indexed([1, 3, 4, 6], (n, i) => n % 2 === 1) // => 3
  * @dataFirst
- * @indexed
- * @pipeable
  * @category Array
  */
+export function findLast<T, S extends T>(
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
+): S | undefined;
 export function findLast<T>(
-  array: ReadonlyArray<T>,
-  fn: Pred<T, boolean>,
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
 ): T | undefined;
 
 /**
- * Returns the value of the last element in the array where predicate is true, and undefined
- * otherwise.
+ * Iterates the array in reverse order and returns the value of the first
+ * element that satisfies the provided testing function. If no elements satisfy
+ * the testing function, undefined is returned.
+ *
+ * Similar functions:
+ * * `find` - If you need the first element that satisfies the provided testing function.
+ * * `findLastIndex` - If you need the index of the found element in the array.
+ * * `lastIndexOf` - If you need to find the index of a value.
+ * * `includes` - If you need to find if a value exists in an array.
+ * * `some` - If you need to find if any element satisfies the provided testing function.
+ * * `filter` - If you need to find all elements that satisfy the provided testing function.
  *
- * @param fn - The predicate.
+ * @param predicate - A function to execute for each element in the array. It
+ * should return `true` to indicate a matching element has been found, and
+ * `false` otherwise. A type-predicate can also be used to narrow the result.
+ * @returns The last (highest-index) element in the array that satisfies the
+ * provided testing function; undefined if no matching element is found.
  * @signature
- *    R.findLast(fn)(items)
- *    R.findLast.indexed(fn)(items)
+ *    R.findLast(predicate)(data)
  * @example
  *    R.pipe(
  *      [1, 3, 4, 6],
  *      R.findLast(n => n % 2 === 1)
  *    ) // => 3
- *    R.pipe(
- *      [1, 3, 4, 6],
- *      R.findLast.indexed((n, i) => n % 2 === 1)
- *    ) // => 3
  * @dataLast
- * @indexed
- * @pipeable
  * @category Array
  */
+export function findLast<T, S extends T>(
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
+): (data: ReadonlyArray<T>) => S | undefined;
 export function findLast<T = never>(
-  fn: Pred<T, boolean>,
-): (array: ReadonlyArray<T>) => T | undefined;
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
+): (data: ReadonlyArray<T>) => T | undefined;
 
 export function findLast(): unknown {
-  return purry(_findLast(false), arguments);
+  // TODO: Use Array.prototype.findLast once we bump our typescript target
+  return purry(findLastImplementation, arguments);
 }
 
-const _findLast =
-  (indexed: boolean) =>
-  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, boolean>) => {
-    for (let i = array.length - 1; i >= 0; i--) {
-      if (indexed ? fn(array[i]!, i, array) : fn(array[i]!)) {
-        return array[i];
-      }
+const findLastImplementation = <T, S extends T>(
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
+): S | undefined => {
+  for (let i = data.length - 1; i >= 0; i--) {
+    const item = data[i]!;
+    if (predicate(item, i, data)) {
+      return item;
     }
-    return;
-  };
-
-export namespace findLast {
-  export function indexed<T>(
-    array: ReadonlyArray<T>,
-    fn: PredIndexed<T, boolean>,
-  ): T | undefined;
-  export function indexed<T>(
-    fn: PredIndexed<T, boolean>,
-  ): (array: ReadonlyArray<T>) => T | undefined;
-
-  export function indexed(): unknown {
-    return purry(_findLast(true), arguments);
   }
-}
+  return;
+};
diff --git a/src/findLastIndex.ts b/src/findLastIndex.ts
index 0f4cecb..661b0fb 100644
--- a/src/findLastIndex.ts
+++ b/src/findLastIndex.ts
@@ -1,78 +1,72 @@
 import { purry } from "./purry";
-import type { Pred, PredIndexedOptional, PredIndexed } from "./_types";
 
 /**
- * Returns the index of the last element in the array where predicate is true, and -1 otherwise.
+ * Iterates the array in reverse order and returns the index of the first
+ * element that satisfies the provided testing function. If no elements satisfy
+ * the testing function, -1 is returned.
  *
- * @param array - The array.
- * @param fn - The predicate.
+ * See also `findLast` which returns the value of last element that satisfies
+ * the testing function (rather than its index).
+ *
+ * @param data - The items to search in.
+ * @param predicate - A function to execute for each element in the array. It
+ * should return `true` to indicate a matching element has been found, and
+ * `false` otherwise.
+ * @returns The index of the last (highest-index) element in the array that
+ * passes the test. Otherwise -1 if no matching element is found.
  * @signature
- *    R.findLastIndex(items, fn)
- *    R.findLastIndex.indexed(items, fn)
+ *    R.findLastIndex(data, predicate)
  * @example
  *    R.findLastIndex([1, 3, 4, 6], n => n % 2 === 1) // => 1
- *    R.findLastIndex.indexed([1, 3, 4, 6], (n, i) => n % 2 === 1) // => 1
  * @dataFirst
- * @indexed
- * @pipeable
  * @category Array
  */
 export function findLastIndex<T>(
-  array: ReadonlyArray<T>,
-  fn: Pred<T, boolean>,
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
 ): number;
 
 /**
- * Returns the index of the last element in the array where predicate is true, and -1 otherwise.
+ * Iterates the array in reverse order and returns the index of the first
+ * element that satisfies the provided testing function. If no elements satisfy
+ * the testing function, -1 is returned.
+ *
+ * See also `findLast` which returns the value of last element that satisfies
+ * the testing function (rather than its index).
  *
- * @param fn - The predicate.
+ * @param predicate - A function to execute for each element in the array. It
+ * should return `true` to indicate a matching element has been found, and
+ * `false` otherwise.
+ * @returns The index of the last (highest-index) element in the array that
+ * passes the test. Otherwise -1 if no matching element is found.
  * @signature
  *    R.findLastIndex(fn)(items)
- *    R.findLastIndex.indexed(fn)(items)
  * @example
  *    R.pipe(
  *      [1, 3, 4, 6],
  *      R.findLastIndex(n => n % 2 === 1)
  *    ) // => 1
- *    R.pipe(
- *      [1, 3, 4, 6],
- *      R.findLastIndex.indexed((n, i) => n % 2 === 1)
- *    ) // => 1
  * @dataLast
- * @indexed
- * @pipeable
  * @category Array
  */
 export function findLastIndex<T>(
-  fn: Pred<T, boolean>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
 ): (array: ReadonlyArray<T>) => number;
 
 export function findLastIndex(): unknown {
-  return purry(_findLastIndex(false), arguments);
+  // TODO: Use `Array.prototype.findLastIndex` once we bump our Typescript target.
+  return purry(findLastIndexImplementation, arguments);
 }
 
-const _findLastIndex =
-  (indexed: boolean) =>
-  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, boolean>) => {
-    for (let i = array.length - 1; i >= 0; i--) {
-      if (indexed ? fn(array[i]!, i, array) : fn(array[i]!)) {
-        return i;
-      }
+const findLastIndexImplementation = <T>(
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
+): number => {
+  for (let i = data.length - 1; i >= 0; i--) {
+    if (predicate(data[i]!, i, data)) {
+      return i;
     }
-
-    return -1;
-  };
-
-export namespace findLastIndex {
-  export function indexed<T>(
-    array: ReadonlyArray<T>,
-    fn: PredIndexed<T, boolean>,
-  ): number;
-  export function indexed<T>(
-    fn: PredIndexed<T, boolean>,
-  ): (array: ReadonlyArray<T>) => number;
-
-  export function indexed(): unknown {
-    return purry(_findLastIndex(true), arguments);
   }
-}
+
+  return -1;
+};
diff --git a/src/forEach.ts b/src/forEach.ts
index 968fd0f..fc16a0d 100644
--- a/src/forEach.ts
+++ b/src/forEach.ts
@@ -1,42 +1,44 @@
-import { _reduceLazy } from "./_reduceLazy";
-import { _toLazyIndexed } from "./_toLazyIndexed";
-import type { Pred, PredIndexed, PredIndexedOptional } from "./_types";
 import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
 /**
- * Iterate an array using a defined callback function. The original array is returned instead of `void`.
+ * Executes a provided function once for each array element. Equivalent to
+ * `Array.prototype.forEach`.
  *
- * @param array - The array.
- * @param fn - The callback function.
- * @returns The original array.
+ * The dataLast version returns the original array (instead of not returning
+ * anything (`void`)) to allow using it in a pipe. When not used in a `pipe` the
+ * returned array is equal to the input array (by reference), and not a shallow
+ * copy of it!
+ *
+ * @param data - The values that would be iterated on.
+ * @param callbackfn - A function to execute for each element in the array.
  * @signature
- *    R.forEach(array, fn)
- *    R.forEach.indexed(array, fn)
+ *    R.forEach(data, callbackfn)
  * @example
  *    R.forEach([1, 2, 3], x => {
  *      console.log(x)
- *    }) // => [1, 2, 3]
- *    R.forEach.indexed([1, 2, 3], (x, i) => {
- *      console.log(x, i)
- *    }) // => [1, 2, 3]
+ *    });
  * @dataFirst
- * @indexed
  * @pipeable
  * @category Array
  */
 export function forEach<T>(
-  array: ReadonlyArray<T>,
-  fn: Pred<T, void>,
-): Array<T>;
+  data: ReadonlyArray<T>,
+  callbackfn: (value: T, index: number, data: ReadonlyArray<T>) => void,
+): void;
 
 /**
- * Iterate an array using a defined callback function. The original array is returned instead of `void`.
+ * Executes a provided function once for each array element. Equivalent to
+ * `Array.prototype.forEach`.
+ *
+ * The dataLast version returns the original array (instead of not returning
+ * anything (`void`)) to allow using it in a pipe. The returned array is the
+ * same reference as the input array, and not a shallow copy of it!
  *
- * @param fn - The function mapper.
+ * @param callbackfn - A function to execute for each element in the array.
+ * @returns The original array (the ref itself, not a shallow copy of it).
  * @signature
- *    R.forEach(fn)(array)
- *    R.forEach.indexed(fn)(array)
+ *    R.forEach(callbackfn)(data)
  * @example
  *    R.pipe(
  *      [1, 2, 3],
@@ -44,61 +46,33 @@ export function forEach<T>(
  *        console.log(x)
  *      })
  *    ) // => [1, 2, 3]
- *    R.pipe(
- *      [1, 2, 3],
- *      R.forEach.indexed((x, i) => {
- *        console.log(x, i)
- *      })
- *    ) // => [1, 2, 3]
  * @dataLast
- * @indexed
  * @pipeable
  * @category Array
  */
 export function forEach<T>(
-  fn: Pred<T, void>,
-): (array: ReadonlyArray<T>) => Array<T>;
+  callbackfn: (value: T, index: number, data: ReadonlyArray<T>) => void,
+): (data: ReadonlyArray<T>) => Array<T>;
 
 export function forEach(): unknown {
-  return purry(_forEach(false), arguments, forEach.lazy);
+  return purry(forEachImplementation, arguments, lazyImplementation);
 }
 
-const _forEach =
-  (indexed: boolean) =>
-  <T, K>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, K>) =>
-    _reduceLazy(
-      array,
-      indexed ? forEach.lazyIndexed(fn) : forEach.lazy(fn),
-      indexed,
-    );
+function forEachImplementation<T>(
+  data: ReadonlyArray<T>,
+  callbackfn: (value: T, index: number, data: ReadonlyArray<T>) => void,
+): Array<T> {
+  // eslint-disable-next-line unicorn/no-array-for-each -- TODO: Once we bump our Typescript target version we can swap this with a proper for loop.
+  data.forEach(callbackfn);
+  // @ts-expect-error [ts4104] - Because the dataFirst signature returns void this is only a problem when the dataLast function is used **outside** of a pipe; for these cases we warn the user that this is happening.
+  return data;
+}
 
-const _lazy =
-  (indexed: boolean) =>
-  <T>(fn: PredIndexedOptional<T, void>): LazyEvaluator<T> =>
-  (value, index, array) => {
-    if (indexed) {
-      fn(value, index, array);
-    } else {
-      fn(value);
-    }
-    return {
-      done: false,
-      hasNext: true,
-      next: value,
-    };
+const lazyImplementation =
+  <T>(
+    callbackfn: (value: T, index: number, data: ReadonlyArray<T>) => void,
+  ): LazyEvaluator<T> =>
+  (value, index, data) => {
+    callbackfn(value, index, data);
+    return { done: false, hasNext: true, next: value };
   };
-
-export namespace forEach {
-  export function indexed<T>(
-    array: ReadonlyArray<T>,
-    fn: PredIndexed<T, void>,
-  ): Array<T>;
-  export function indexed<T>(
-    fn: PredIndexed<T, void>,
-  ): (array: ReadonlyArray<T>) => Array<T>;
-  export function indexed(): unknown {
-    return purry(_forEach(true), arguments, forEach.lazyIndexed);
-  }
-  export const lazy = _lazy(false);
-  export const lazyIndexed = _toLazyIndexed(_lazy(true));
-}
diff --git a/src/forEachObj.ts b/src/forEachObj.ts
index 22aa5c6..255ef6b 100644
--- a/src/forEachObj.ts
+++ b/src/forEachObj.ts
@@ -1,94 +1,68 @@
 import { purry } from "./purry";
 
-type IndexedIteratee<
-  T extends Record<PropertyKey, unknown>,
-  K extends keyof T,
-> = (value: T[K], key: K, obj: T) => void;
-type UnindexedIteratee<T extends Record<PropertyKey, unknown>> = (
-  value: T[keyof T],
-) => void;
-
 /**
- * Iterate an object using a defined callback function. The original object is returned.
+ * Iterate an object using a defined callback function.
+ *
+ * The dataLast version returns the original object (instead of not returning
+ * anything (`void`)) to allow using it in a pipe. The returned object is the
+ * same reference as the input object, and not a shallow copy of it!
  *
- * @param object - The object.
- * @param fn - The callback function.
- * @returns The original object.
+ * @param data - The object who'se entries would be iterated on.
+ * @param callbackfn - A function to execute for each element in the array.
  * @signature
  *    R.forEachObj(object, fn)
  * @example
- *    R.forEachObj({a: 1}, (val) => {
- *      console.log(`${val}`)
- *    }) // "1"
- *    R.forEachObj.indexed({a: 1}, (val, key, obj) => {
+ *    R.forEachObj({a: 1}, (val, key, obj) => {
  *      console.log(`${key}: ${val}`)
  *    }) // "a: 1"
  * @dataFirst
  * @category Object
  */
 export function forEachObj<T extends Record<PropertyKey, unknown>>(
-  object: T,
-  fn: UnindexedIteratee<T>,
-): T;
+  data: T,
+  callbackfn: (value: T[keyof T], key: keyof T, obj: T) => void,
+): void;
 
 /**
- * Iterate an object using a defined callback function. The original object is returned.
+ * Iterate an object using a defined callback function.
+ *
+ * The dataLast version returns the original object (instead of not returning
+ * anything (`void`)) to allow using it in a pipe. The returned object is the
+ * same reference as the input object, and not a shallow copy of it!
  *
- * @param fn - The callback function.
+ * @param callbackfn - A function to execute for each element in the array.
+ * @returns The original object (the ref itself, not a shallow copy of it).
  * @signature
  *    R.forEachObj(fn)(object)
  * @example
- *  R.pipe(
- *      {a: 1},
- *      R.forEachObj((val) => console.log(`${val}`))
- *    ) // "1"
  *    R.pipe(
  *      {a: 1},
- *      R.forEachObj.indexed((val, key) => console.log(`${key}: ${val}`))
+ *      R.forEachObj((val, key) => console.log(`${key}: ${val}`))
  *    ) // "a: 1"
  * @dataLast
  * @category Object
  */
 export function forEachObj<T extends Record<PropertyKey, unknown>>(
-  fn: UnindexedIteratee<T>,
+  callbackfn: (value: T[keyof T], key: keyof T, obj: T) => void,
 ): (object: T) => T;
 
 export function forEachObj(): unknown {
-  return purry(_forEachObj(false), arguments);
+  return purry(forEachObjImplementation, arguments);
 }
 
-const _forEachObj =
-  (indexed: boolean) =>
-  <T extends Record<PropertyKey, unknown>>(
-    data: T,
-    fn: (
-      value: T[Extract<keyof T, string>],
-      key?: Extract<keyof T, string>,
-      obj?: T,
-    ) => void,
-  ) => {
-    for (const key in data) {
-      if (Object.prototype.hasOwnProperty.call(data, key)) {
-        const { [key]: val } = data;
-        if (indexed) {
-          fn(val, key, data);
-        } else {
-          fn(val);
-        }
-      }
+function forEachObjImplementation<T extends Record<PropertyKey, unknown>>(
+  data: T,
+  fn: (
+    value: T[Extract<keyof T, string>],
+    key?: Extract<keyof T, string>,
+    obj?: T,
+  ) => void,
+): T {
+  for (const key in data) {
+    if (Object.prototype.hasOwnProperty.call(data, key)) {
+      const { [key]: val } = data;
+      fn(val, key, data);
     }
-    return data;
-  };
-
-export namespace forEachObj {
-  export function indexed<T extends Record<PropertyKey, unknown>>(
-    object: T,
-    fn: IndexedIteratee<T, keyof T>,
-  ): T;
-  export function indexed<T extends Record<PropertyKey, unknown>>(
-    fn: IndexedIteratee<T, keyof T>,
-  ): (object: T) => T;
-  export function indexed(): unknown {
-    return purry(_forEachObj(true), arguments);
   }
+  return data;
 }
diff --git a/src/groupBy.ts b/src/groupBy.ts
index a4b23f7..0c4f3ec 100644
--- a/src/groupBy.ts
+++ b/src/groupBy.ts
@@ -1,99 +1,99 @@
-import type {
-  ExactRecord,
-  NonEmptyArray,
-  PredIndexed,
-  PredIndexedOptional,
-} from "./_types";
+import type { ExactRecord, NonEmptyArray } from "./_types";
 import { purry } from "./purry";
 
 /**
- * Splits a collection into sets, grouped by the result of running each value
- * through `fn`.
+ * Groups the elements of a given iterable according to the string values
+ * returned by a provided callback function. The returned object has separate
+ * properties for each group, containing arrays with the elements in the group.
+ * Unlike the built in `Object.groupBy` this function also allows the callback to
+ * return `undefined` in order to exclude the item from being added to any
+ * group.
  *
- * @param items - The items to group.
- * @param fn - The grouping function. When `undefined` is returned the item
- * would be skipped and not grouped under any key.
+ * @param data - The items to group.
+ * @param callbackfn - A function to execute for each element in the iterable.
+ * It should return a value indicating the group of the current element, or
+ * `undefined` when the item should be excluded from any group.
+ * @returns An object with properties for all groups, each assigned to an array
+ * containing the elements of the associated group.
  * @signature
- *    R.groupBy(array, fn)
+ *    R.groupBy(data, callbackfn)
  * @example
  *    R.groupBy([{a: 'cat'}, {a: 'dog'}] as const, R.prop('a')) // => {cat: [{a: 'cat'}], dog: [{a: 'dog'}]}
  *    R.groupBy([0, 1], x => x % 2 === 0 ? 'even' : undefined) // => {even: [0]}
  * @dataFirst
- * @indexed
  * @category Array
  */
 export function groupBy<T, Key extends PropertyKey = PropertyKey>(
-  items: ReadonlyArray<T>,
-  fn: (item: T) => Key | undefined,
+  data: ReadonlyArray<T>,
+  callbackfn: (
+    value: T,
+    index: number,
+    data: ReadonlyArray<T>,
+  ) => Key | undefined,
 ): ExactRecord<Key, NonEmptyArray<T>>;
 
 /**
- * Splits a collection into sets, grouped by the result of running each value
- * through `fn`.
+ * Groups the elements of a given iterable according to the string values
+ * returned by a provided callback function. The returned object has separate
+ * properties for each group, containing arrays with the elements in the group.
+ * Unlike the built in `Object.groupBy` this function also allows the callback to
+ * return `undefined` in order to exclude the item from being added to any
+ * group.
  *
- * @param fn - The grouping function. When `undefined` is returned the item
- * would be skipped and not grouped under any key.
+ * @param callbackfn - A function to execute for each element in the iterable.
+ * It should return a value indicating the group of the current element, or
+ * `undefined` when the item should be excluded from any group.
+ * @returns An object with properties for all groups, each assigned to an array
+ * containing the elements of the associated group.
  * @signature
- *    R.groupBy(fn)(array);
+ *    R.groupBy(callbackfn)(data);
  * @example
  *    R.pipe(
  *      [{a: 'cat'}, {a: 'dog'}] as const,
- *      R.groupBy(R.prop('a'),
+ *      R.groupBy(R.prop('a')),
  *    ); // => {cat: [{a: 'cat'}], dog: [{a: 'dog'}]}
  *    R.pipe(
  *      [0, 1],
  *      R.groupBy(x => x % 2 === 0 ? 'even' : undefined),
  *    ); // => {even: [0]}
  * @dataLast
- * @indexed
  * @category Array
  */
 export function groupBy<T, Key extends PropertyKey = PropertyKey>(
-  fn: (item: T) => Key | undefined,
+  callbackfn: (
+    value: T,
+    index: number,
+    data: ReadonlyArray<T>,
+  ) => Key | undefined,
 ): (items: ReadonlyArray<T>) => ExactRecord<Key, NonEmptyArray<T>>;
 
 export function groupBy(): unknown {
-  return purry(_groupBy(false), arguments);
+  return purry(groupByImplementation, arguments);
 }
 
-const _groupBy =
-  (indexed: boolean) =>
-  <T, Key extends PropertyKey = PropertyKey>(
-    array: ReadonlyArray<T>,
-    fn: PredIndexedOptional<T, Key | undefined>,
-  ) => {
-    const ret: Record<string, Array<T>> = {};
+const groupByImplementation = <T, Key extends PropertyKey = PropertyKey>(
+  data: ReadonlyArray<T>,
+  callbackfn: (
+    value: T,
+    index: number,
+    data: ReadonlyArray<T>,
+  ) => Key | undefined,
+): ExactRecord<Key, NonEmptyArray<T>> => {
+  const output: Partial<Record<Key, Array<T>>> = {};
 
-    for (let index = 0; index < array.length; index++) {
-      // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
-      const item = array[index]!;
-      const key = indexed ? fn(item, index, array) : fn(item);
-      if (key !== undefined) {
-        const actualKey = String(key);
-        // eslint-disable-next-line @typescript-eslint/prefer-destructuring
-        let items = ret[actualKey];
-        if (items === undefined) {
-          items = [];
-          ret[actualKey] = items;
-        }
-        items.push(item);
+  for (let index = 0; index < data.length; index++) {
+    // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
+    const item = data[index]!;
+    const key = callbackfn(item, index, data);
+    if (key !== undefined) {
+      let { [key]: items } = output;
+      if (items === undefined) {
+        items = [];
+        output[key] = items;
       }
+      items.push(item);
     }
-
-    return ret;
-  };
-
-export namespace groupBy {
-  export function indexed<T, Key extends PropertyKey = PropertyKey>(
-    items: ReadonlyArray<T>,
-    fn: PredIndexed<T, Key | undefined>,
-  ): ExactRecord<Key, NonEmptyArray<T>>;
-
-  export function indexed<Value, Key extends PropertyKey = PropertyKey>(
-    fn: PredIndexed<Value, Key | undefined>,
-  ): (items: ReadonlyArray<Value>) => ExactRecord<Key, NonEmptyArray<Value>>;
-
-  export function indexed(): unknown {
-    return purry(_groupBy(true), arguments);
   }
-}
+
+  return output as ExactRecord<Key, NonEmptyArray<T>>;
+};
diff --git a/src/map.ts b/src/map.ts
index 1d1287e..82dbe8b 100644
--- a/src/map.ts
+++ b/src/map.ts
@@ -1,102 +1,68 @@
-import { _reduceLazy } from "./_reduceLazy";
-import { _toLazyIndexed } from "./_toLazyIndexed";
-import type {
-  IterableContainer,
-  Mapped,
-  PredIndexed,
-  PredIndexedOptional,
-} from "./_types";
+import type { IterableContainer, Mapped } from "./_types";
 import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
 /**
- * Creates a new array populated with the results of calling `mapper` on every
- * element in the calling array.
+ * Creates a new array populated with the results of calling a provided function
+ * on every element in the calling array. Equivalent to `Array.prototype.map`.
  *
  * @param data - The array to map.
- * @param mapper - A function to execute for each element in the array. Its
+ * @param callbackfn - A function to execute for each element in the array. Its
  * return value is added as a single element in the new array.
  * @returns A new array with each element being the result of the callback
  * function.
  * @signature
- *    R.map(array, mapper)
- *    R.map.indexed(array, mapper)
+ *    R.map(data, callbackfn)
  * @example
  *    R.map([1, 2, 3], R.multiply(2)); // => [2, 4, 6]
  *    R.map([0, 0], R.add(1)); // => [1, 1]
- *    R.map.indexed([0, 0, 0], (_, i) => i); // => [0, 1, 2]
- *    R.map.indexed([0, 0], (x, i) => x + i); // => [0, 1]
+ *    R.map([0, 0], (value, index) => value + index); // => [0, 1]
  * @dataFirst
- * @indexed
  * @pipeable
  * @category Array
  */
-export function map<T extends IterableContainer, K>(
+export function map<T extends IterableContainer, U>(
   data: T,
-  mapper: (item: T[number]) => K,
-): Mapped<T, K>;
+  callbackfn: (value: T[number], index: number, data: T) => U,
+): Mapped<T, U>;
 
 /**
- * Creates a new array populated with the results of calling `mapper` on every
- * element in the calling array.
+ * Creates a new array populated with the results of calling a provided function
+ * on every element in the calling array. Equivalent to `Array.prototype.map`.
  *
- * @param mapper - A function to execute for each element in the array. Its
+ * @param callbackfn - A function to execute for each element in the array. Its
  * return value is added as a single element in the new array.
  * @returns A new array with each element being the result of the callback
  * function.
  * @signature
- *    R.map(mapper)(array)
- *    R.map.indexed(mapper)(array)
+ *    R.map(callbackfn)(data)
  * @example
  *    R.pipe([1, 2, 3], R.map(R.multiply(2))); // => [2, 4, 6]
  *    R.pipe([0, 0], R.map(R.add(1))); // => [1, 1]
- *    R.pipe([0, 0, 0], R.map.indexed((_, i) => i)); // => [0, 1, 2]
- *    R.pipe([0, 0], R.map.indexed((x, i) => x + i)); // => [0, 1]
+ *    R.pipe([0, 0], R.map((value, index) => value + index)); // => [0, 1]
  * @dataLast
- * @indexed
  * @pipeable
  * @category Array
  */
-export function map<T extends IterableContainer, K>(
-  mapper: (item: T[number]) => K,
-): (items: T) => Mapped<T, K>;
+export function map<T extends IterableContainer, U>(
+  callbackfn: (value: T[number], index: number, data: T) => U,
+): (data: T) => Mapped<T, U>;
 
 export function map(): unknown {
-  return purry(_map(false), arguments, map.lazy);
+  return purry(mapImplementation, arguments, lazyImplementation);
 }
 
-const _map =
-  (indexed: boolean) =>
-  <T, K>(array: ReadonlyArray<T>, mapper: PredIndexedOptional<T, K>) =>
-    _reduceLazy(
-      array,
-      indexed ? map.lazyIndexed(mapper) : map.lazy(mapper),
-      indexed,
-    );
+const mapImplementation = <T, U>(
+  data: ReadonlyArray<T>,
+  callbackfn: (value: T, index: number, data: ReadonlyArray<T>) => U,
+): Array<U> => data.map(callbackfn);
 
-const _lazy =
-  (indexed: boolean) =>
-  <T, K>(mapper: PredIndexedOptional<T, K>): LazyEvaluator<T, K> =>
-  (value, index, array) => ({
+const lazyImplementation =
+  <T, U>(
+    callbackfn: (value: T, index: number, data: ReadonlyArray<T>) => U,
+  ): LazyEvaluator<T, U> =>
+  (value, index, data) => ({
     done: false,
     hasNext: true,
-    next: indexed ? mapper(value, index, array) : mapper(value),
+    next: callbackfn(value, index, data),
   });
-
-export namespace map {
-  export function indexed<T extends IterableContainer, K>(
-    items: T,
-    mapper: PredIndexed<T[number], K>,
-  ): Mapped<T, K>;
-
-  export function indexed<T extends IterableContainer, K>(
-    mapper: PredIndexed<T[number], K>,
-  ): (items: T) => Mapped<T, K>;
-
-  export function indexed(): unknown {
-    return purry(_map(true), arguments, map.lazyIndexed);
-  }
-
-  export const lazy = _lazy(false);
-  export const lazyIndexed = _toLazyIndexed(_lazy(true));
-}
diff --git a/src/mapToObj.ts b/src/mapToObj.ts
index fe663b8..41e0d40 100644
--- a/src/mapToObj.ts
+++ b/src/mapToObj.ts
@@ -1,4 +1,3 @@
-import type { PredIndexedOptional } from "./_types";
 import { purry } from "./purry";
 
 /**
@@ -17,17 +16,14 @@ import { purry } from "./purry";
  * @returns The new mapped object.
  * @signature
  *    R.mapToObj(array, fn)
- *    R.mapToObj.indexed(array, fn)
  * @example
  *    R.mapToObj([1, 2, 3], x => [String(x), x * 2]) // => {1: 2, 2: 4, 3: 6}
- *    R.mapToObj.indexed([0, 0, 0], (x, i) => [i, i]) // => {0: 0, 1: 1, 2: 2}
  * @dataFirst
- * @indexed
  * @category Array
  */
 export function mapToObj<T, K extends PropertyKey, V>(
   array: ReadonlyArray<T>,
-  fn: (element: T) => [K, V],
+  fn: (value: T, index: number, data: ReadonlyArray<T>) => [K, V],
 ): Record<K, V>;
 
 /**
@@ -45,56 +41,39 @@ export function mapToObj<T, K extends PropertyKey, V>(
  * @returns The new mapped object.
  * @signature
  *    R.mapToObj(fn)(array)
- *    R.mapToObj.indexed(fn)(array)
  * @example
  *    R.pipe(
  *      [1, 2, 3],
  *      R.mapToObj(x => [String(x), x * 2])
  *    ) // => {1: 2, 2: 4, 3: 6}
- *    R.pipe(
- *      [0, 0, 0],
- *      R.mapToObj.indexed((x, i) => [i, i])
- *    ) // => {0: 0, 1: 1, 2: 2}
  * @dataLast
- * @indexed
  * @category Array
  */
 export function mapToObj<T, K extends PropertyKey, V>(
-  fn: (element: T) => [K, V],
+  fn: (value: T, index: number, data: ReadonlyArray<T>) => [K, V],
 ): (array: ReadonlyArray<T>) => Record<K, V>;
 
 export function mapToObj(): unknown {
-  return purry(_mapToObj(false), arguments);
+  return purry(mapToObjImplementation, arguments);
 }
 
-const _mapToObj =
-  (indexed: boolean) =>
-  (
-    array: ReadonlyArray<unknown>,
-    fn: PredIndexedOptional<unknown, [PropertyKey, unknown]>,
-  ) => {
-    const out: Record<PropertyKey, unknown> = {};
-
-    for (let index = 0; index < array.length; index++) {
-      // TODO: Use Array.prototype.entries once we bump the Typescript target version to iterate over the elements and index at the same time.
-      // eslint-disable-next-line @typescript-eslint/prefer-destructuring
-      const element = array[index];
-      const [key, value] = indexed ? fn(element, index, array) : fn(element);
-      out[key] = value;
-    }
+function mapToObjImplementation(
+  array: ReadonlyArray<unknown>,
+  fn: (
+    value: unknown,
+    index: number,
+    data: ReadonlyArray<unknown>,
+  ) => [PropertyKey, unknown],
+): Record<PropertyKey, unknown> {
+  const out: Record<PropertyKey, unknown> = {};
 
-    return out;
-  };
-
-export namespace mapToObj {
-  export function indexed<T, K extends PropertyKey, V>(
-    array: ReadonlyArray<T>,
-    fn: (element: T, index: number, array: ReadonlyArray<T>) => [K, V],
-  ): Record<K, V>;
-  export function indexed<T, K extends PropertyKey, V>(
-    fn: (element: T, index: number, array: ReadonlyArray<T>) => [K, V],
-  ): (array: ReadonlyArray<T>) => Record<K, V>;
-  export function indexed(): unknown {
-    return purry(_mapToObj(true), arguments);
+  for (let index = 0; index < array.length; index++) {
+    // TODO: Use Array.prototype.entries once we bump the Typescript target version to iterate over the elements and index at the same time.
+    // eslint-disable-next-line @typescript-eslint/prefer-destructuring
+    const element = array[index];
+    const [key, value] = fn(element, index, array);
+    out[key] = value;
   }
+
+  return out;
 }
diff --git a/src/mapWithFeedback.ts b/src/mapWithFeedback.ts
index 9295497..910d07d 100644
--- a/src/mapWithFeedback.ts
+++ b/src/mapWithFeedback.ts
@@ -1,133 +1,100 @@
 import { _reduceLazy } from "./_reduceLazy";
-import { _toLazyIndexed } from "./_toLazyIndexed";
 import type { IterableContainer, Mapped } from "./_types";
 import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
 /**
- * Applies a function on each element of the array, using the result of the previous application, and returns an array of the successively computed values.
+ * Applies a function on each element of the array, using the result of the
+ * previous application, and returns an array of the successively computed
+ * values.
  *
- * @param array - The array to map over.
- * @param reducer - The callback function that receives the previous value, the current element, and optionally the index and the whole array.
+ * @param data - The array to map over.
+ * @param callbackfn - The callback function that receives the previous value,
+ * the current element.
  * @param initialValue - The initial value to start the computation with.
- * @returns An array of successively computed values from the left side of the array.
+ * @returns An array of successively computed values from the left side of the
+ * array.
  * @signature
- *    R.mapWithFeedback(items, fn, initialValue)
- *    R.mapWithFeedback.indexed(items, fn, initialValue)
+ *    R.mapWithFeedback(data, callbackfn, initialValue);
  * @example
- *    R.mapWithFeedback([1, 2, 3, 4, 5], (prev, x) => prev + x, 100) // => [101, 103, 106, 110, 115]
- *    R.mapWithFeedback.indexed([1, 2, 3, 4, 5], (prev, x, i, array) => prev + x, 100) // => [101, 103, 106, 110, 115]
+ *    R.mapWithFeedback(
+ *      [1, 2, 3, 4, 5],
+ *      (prev, x) => prev + x,
+ *      100,
+ *    ); // => [101, 103, 106, 110, 115]
  * @dataFirst
- * @indexed
  * @pipeable
  * @category Array
  */
 export function mapWithFeedback<T extends IterableContainer, U>(
-  array: T,
-  reducer: (previousValue: U, currentValue: T[number]) => U,
+  data: T,
+  callbackfn: (
+    previousValue: U,
+    currentValue: T[number],
+    currentIndex: number,
+    data: T,
+  ) => U,
   initialValue: U,
 ): Mapped<T, U>;
 
 /**
- * Applies a function on each element of the array, using the result of the previous application, and returns an array of the successively computed values.
+ * Applies a function on each element of the array, using the result of the
+ * previous application, and returns an array of the successively computed
+ * values.
  *
- * @param reducer - The callback function that receives the previous value, the current element, and optionally the index and the whole array.
+ * @param callbackfn - The callback function that receives the previous value,
+ * the current element.
  * @param initialValue - The initial value to start the computation with.
- * @returns An array of successively computed values from the left side of the array.
+ * @returns An array of successively computed values from the left side of the
+ * array.
  * @signature
- *    R.mapWithFeedback(fn, initialValue)(array)
+ *    R.mapWithFeedback(callbackfn, initialValue)(data);
  * @example
- *    R.pipe([1, 2, 3, 4, 5], R.mapWithFeedback((prev, x) => prev + x, 100)) // => [101, 103, 106, 110, 115]
- *    R.pipe([1, 2, 3, 4, 5], R.mapWithFeedback.indexed((prev, x, i, array) => prev + x, 100)) // => [101, 103, 106, 110, 115]
+ *    R.pipe(
+ *      [1, 2, 3, 4, 5],
+ *      R.mapWithFeedback((prev, x) => prev + x, 100),
+ *    ); // => [101, 103, 106, 110, 115]
  * @dataLast
- * @indexed
  * @pipeable
  * @category Array
  */
 export function mapWithFeedback<T extends IterableContainer, U>(
-  reducer: (previousValue: U, currentValue: T[number]) => U,
+  callbackfn: (
+    previousValue: U,
+    currentValue: T[number],
+    currentIndex: number,
+    data: T,
+  ) => U,
   initialValue: U,
-): (items: T) => Mapped<T, U>;
+): (data: T) => Mapped<T, U>;
 
 export function mapWithFeedback(): unknown {
-  return purry(
-    mapWithFeedbackImplementation(false),
-    arguments,
-    mapWithFeedback.lazy,
-  );
+  return purry(mapWithFeedbackImplementation, arguments, lazyImplementation);
 }
 
-const mapWithFeedbackImplementation =
-  (indexed: boolean) =>
-  <T, U>(
-    items: ReadonlyArray<T>,
-    reducer: (
-      previousValue: U,
-      currentValue: T,
-      index?: number,
-      items?: ReadonlyArray<T>,
-    ) => U,
-    initialValue: U,
-  ) => {
-    const implementation = indexed
-      ? mapWithFeedback.lazyIndexed
-      : mapWithFeedback.lazy;
-
-    return _reduceLazy(items, implementation(reducer, initialValue), indexed);
-  };
+const mapWithFeedbackImplementation = <T, U>(
+  data: ReadonlyArray<T>,
+  reducer: (
+    previousValue: U,
+    currentValue: T,
+    index: number,
+    data: ReadonlyArray<T>,
+  ) => U,
+  initialValue: U,
+): Array<U> => _reduceLazy(data, lazyImplementation(reducer, initialValue));
 
-const lazyImplementation =
-  (indexed: boolean) =>
-  <T, U>(
-    reducer: (
-      previousValue: U,
-      currentValue: T,
-      index?: number,
-      items?: ReadonlyArray<T>,
-    ) => U,
-    initialValue: U,
-  ): LazyEvaluator<T, U> => {
-    let previousValue = initialValue;
-    return (value, index, items) => {
-      previousValue = indexed
-        ? reducer(previousValue, value, index, items)
-        : reducer(previousValue, value);
-      return {
-        done: false,
-        hasNext: true,
-        next: previousValue,
-      };
-    };
+const lazyImplementation = <T, U>(
+  reducer: (
+    previousValue: U,
+    currentValue: T,
+    index: number,
+    data: ReadonlyArray<T>,
+  ) => U,
+  initialValue: U,
+): LazyEvaluator<T, U> => {
+  let previousValue = initialValue;
+  return (currentValue, index, data) => {
+    previousValue = reducer(previousValue, currentValue, index, data);
+    return { done: false, hasNext: true, next: previousValue };
   };
-
-export namespace mapWithFeedback {
-  export function indexed<T extends IterableContainer, U>(
-    items: T,
-    reducer: (
-      previousValue: U,
-      currentValue: T[number],
-      index: number,
-      items: T,
-    ) => U,
-    initialValue: U,
-  ): Mapped<T, U>;
-  export function indexed<T extends IterableContainer, U>(
-    reducer: (
-      previousValue: U,
-      currentValue: T[number],
-      index: number,
-      items: T,
-    ) => U,
-    initialValue: U,
-  ): (items: T) => Mapped<T, U>;
-  export function indexed(): unknown {
-    return purry(
-      mapWithFeedbackImplementation(true),
-      arguments,
-      mapWithFeedback.lazyIndexed,
-    );
-  }
-
-  export const lazy = lazyImplementation(false);
-  export const lazyIndexed = _toLazyIndexed(lazyImplementation(true));
-}
+};
diff --git a/src/meanBy.ts b/src/meanBy.ts
index b2f0f06..7567f7b 100644
--- a/src/meanBy.ts
+++ b/src/meanBy.ts
@@ -1,23 +1,4 @@
 import { purry } from "./purry";
-import type { PredIndexed, PredIndexedOptional } from "./_types";
-
-const _meanBy =
-  (indexed: boolean) =>
-  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, number>) => {
-    if (array.length === 0) {
-      return NaN;
-    }
-
-    let sum = 0;
-
-    for (let index = 0; index < array.length; index++) {
-      // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
-      const item = array[index]!;
-      sum += indexed ? fn(item, index, array) : fn(item);
-    }
-
-    return sum / array.length;
-  };
 
 /**
  * Returns the mean of the elements of an array using the provided predicate.
@@ -25,19 +6,17 @@ const _meanBy =
  * @param fn - Predicate function.
  * @signature
  *   R.meanBy(fn)(array)
- *   R.meanBy.indexed(fn)(array)
  * @example
  *    R.pipe(
  *      [{a: 5}, {a: 1}, {a: 3}],
  *      R.meanBy(x => x.a)
  *    ) // 3
  * @dataLast
- * @indexed
  * @category Array
  */
 
 export function meanBy<T>(
-  fn: (item: T) => number,
+  fn: (value: T, index: number, data: ReadonlyArray<T>) => number,
 ): (items: ReadonlyArray<T>) => number;
 
 /**
@@ -47,37 +26,39 @@ export function meanBy<T>(
  * @param fn - Predicate function.
  * @signature
  *   R.meanBy(array, fn)
- *   R.meanBy.indexed(array, fn)
  * @example
  *    R.meanBy(
  *      [{a: 5}, {a: 1}, {a: 3}],
  *      x => x.a
  *    ) // 3
  * @dataFirst
- * @indexed
  * @category Array
  */
 
 export function meanBy<T>(
   items: ReadonlyArray<T>,
-  fn: (item: T) => number,
+  fn: (value: T, index: number, data: ReadonlyArray<T>) => number,
 ): number;
 
 export function meanBy(): unknown {
-  return purry(_meanBy(false), arguments);
+  return purry(meanByImplementation, arguments);
 }
 
-export namespace meanBy {
-  export function indexed<T>(
-    array: ReadonlyArray<T>,
-    fn: PredIndexed<T, number>,
-  ): number;
+const meanByImplementation = <T>(
+  array: ReadonlyArray<T>,
+  fn: (value: T, index: number, data: ReadonlyArray<T>) => number,
+): number => {
+  if (array.length === 0) {
+    return NaN;
+  }
 
-  export function indexed<T>(
-    fn: PredIndexed<T, number>,
-  ): (array: ReadonlyArray<T>) => number;
+  let sum = 0;
 
-  export function indexed(): unknown {
-    return purry(_meanBy(true), arguments);
+  for (let index = 0; index < array.length; index++) {
+    // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
+    const item = array[index]!;
+    sum += fn(item, index, array);
   }
-}
+
+  return sum / array.length;
+};
diff --git a/src/partition.ts b/src/partition.ts
index 23b0ee2..cd73015 100644
--- a/src/partition.ts
+++ b/src/partition.ts
@@ -1,104 +1,78 @@
 import { purry } from "./purry";
-import type { PredIndexedOptional, PredIndexed } from "./_types";
 
 /**
- * Splits a collection into two groups, the first of which contains elements the `predicate` type guard passes, and the second one containing the rest.
+ * Splits a collection into two groups, the first of which contains elements the
+ * `predicate` type guard passes, and the second one containing the rest.
  *
- * @param items - The items to split.
- * @param predicate - A type guard function to invoke on every item.
- * @returns The array of grouped elements.
+ * @param data - The items to split.
+ * @param predicate - A function to execute for each element in the array. It
+ * should return `true` to add the element to the first partition, and and
+ * `false` to add the element to the other partition. A type-predicate can also
+ * be used to narrow the result.
+ * @returns A 2-tuple of arrays where the first array contains the elements that
+ * passed the predicate, and the second array contains the elements that did
+ * not. The items are in the same order as they were in the original array.
  * @signature
- *    R.partition(array, fn)
+ *    R.partition(data, predicate)
  * @example
- *    R.partition(['one', 'two', 'forty two'], x => x.length === 3) // => [['one', 'two'], ['forty two']]
+ *    R.partition(
+ *      ['one', 'two', 'forty two'],
+ *      x => x.length === 3,
+ *    ); // => [['one', 'two'], ['forty two']]
  * @dataFirst
- * @indexed
  * @category Array
  */
 export function partition<T, S extends T>(
-  items: ReadonlyArray<T>,
-  predicate: (item: T) => item is S,
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
 ): [Array<S>, Array<Exclude<T, S>>];
-
-/**
- * Splits a collection into two groups, the first of which contains elements the `predicate` function matches, and the second one containing the rest.
- *
- * @param items - The items to split.
- * @param predicate - The function invoked per iteration.
- * @returns The array of grouped elements.
- * @signature
- *    R.partition(array, fn)
- * @example
- *    R.partition(['one', 'two', 'forty two'], x => x.length === 3) // => [['one', 'two'], ['forty two']]
- * @dataFirst
- * @indexed
- * @category Array
- */
 export function partition<T>(
-  items: ReadonlyArray<T>,
-  predicate: (item: T) => boolean,
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
 ): [Array<T>, Array<T>];
 
 /**
- * Splits a collection into two groups, the first of which contains elements the `predicate` type guard passes, and the second one containing the rest.
+ * Splits a collection into two groups, the first of which contains elements the
+ * `predicate` type guard passes, and the second one containing the rest.
  *
- * @param predicate - The grouping function.
- * @returns The array of grouped elements.
+ * @param predicate - A function to execute for each element in the array. It
+ * should return `true` to add the element to the first partition, and and
+ * `false` to add the element to the other partition. A type-predicate can also
+ * be used to narrow the result.
+ * @returns A 2-tuple of arrays where the first array contains the elements that
+ * passed the predicate, and the second array contains the elements that did
+ * not. The items are in the same order as they were in the original array.
  * @signature
- *    R.partition(fn)(array)
+ *    R.partition(predicate)(data)
  * @example
- *    R.pipe(['one', 'two', 'forty two'], R.partition(x => x.length === 3)) // => [['one', 'two'], ['forty two']]
+ *    R.pipe(
+ *      ['one', 'two', 'forty two'],
+ *      R.partition(x => x.length === 3),
+ *    ); // => [['one', 'two'], ['forty two']]
  * @dataLast
- * @indexed
  * @category Array
  */
 export function partition<T, S extends T>(
-  predicate: (item: T) => item is S,
-): (array: ReadonlyArray<T>) => [Array<S>, Array<Exclude<T, S>>];
-
-/**
- * Splits a collection into two groups, the first of which contains elements the `predicate` function matches, and the second one containing the rest.
- *
- * @param predicate - The grouping function.
- * @returns The array of grouped elements.
- * @signature
- *    R.partition(fn)(array)
- * @example
- *    R.pipe(['one', 'two', 'forty two'], R.partition(x => x.length === 3)) // => [['one', 'two'], ['forty two']]
- * @dataLast
- * @indexed
- * @category Array
- */
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
+): (data: ReadonlyArray<T>) => [Array<S>, Array<Exclude<T, S>>];
 export function partition<T>(
-  predicate: (item: T) => boolean,
-): (array: ReadonlyArray<T>) => [Array<T>, Array<T>];
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
+): (data: ReadonlyArray<T>) => [Array<T>, Array<T>];
 
 export function partition(): unknown {
-  return purry(_partition(false), arguments);
+  return purry(partitionImplementation, arguments);
 }
 
-const _partition =
-  (indexed: boolean) =>
-  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, boolean>) => {
-    const ret: [Array<T>, Array<T>] = [[], []];
-    for (let index = 0; index < array.length; index++) {
-      // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
-      const item = array[index]!;
-      const matches = indexed ? fn(item, index, array) : fn(item);
-      ret[matches ? 0 : 1].push(item);
-    }
-    return ret;
-  };
-
-export namespace partition {
-  export function indexed<T>(
-    array: ReadonlyArray<T>,
-    predicate: PredIndexed<T, boolean>,
-  ): [Array<T>, Array<T>];
-  export function indexed<T>(
-    predicate: PredIndexed<T, boolean>,
-  ): (array: ReadonlyArray<T>) => [Array<T>, Array<T>];
-  export function indexed(): unknown {
-    return purry(_partition(true), arguments);
+const partitionImplementation = <T, S extends T>(
+  data: ReadonlyArray<T>,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => value is S,
+): [Array<T>, Array<T>] => {
+  const ret: [Array<T>, Array<T>] = [[], []];
+  for (let index = 0; index < data.length; index++) {
+    // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
+    const item = data[index]!;
+    const matches = predicate(item, index, data);
+    ret[matches ? 0 : 1].push(item);
   }
-}
+  return ret;
+};
diff --git a/src/pipe.ts b/src/pipe.ts
index 1655972..5435ff9 100644
--- a/src/pipe.ts
+++ b/src/pipe.ts
@@ -3,8 +3,8 @@
 
 export type LazyEvaluator<T = unknown, R = T> = (
   item: T,
-  index?: number,
-  data?: ReadonlyArray<T>,
+  index: number,
+  data: ReadonlyArray<T>,
 ) => LazyResult<R>;
 
 type LazyResult<T> = LazyEmpty | LazyMany<T> | LazyNext<T>;
@@ -31,7 +31,6 @@ type LazyMany<T> = {
 };
 
 type PreparedLazyOperation = LazyEvaluator & {
-  readonly isIndexed: boolean;
   readonly isSingle: boolean;
 
   // These are intentionally mutable, they maintain the lazy piped state.
@@ -39,6 +38,19 @@ type PreparedLazyOperation = LazyEvaluator & {
   items: Array<unknown>;
 };
 
+type LazyFn = (
+  value: unknown,
+  index: number,
+  items: ReadonlyArray<unknown>,
+) => LazyResult<unknown>;
+
+type LazyOp = ((input: unknown) => unknown) & {
+  readonly lazy: ((...args: any) => LazyFn) & {
+    readonly single: boolean;
+  };
+  readonly lazyArgs?: ReadonlyArray<unknown>;
+};
+
 /**
  * Perform left-to-right function composition.
  *
@@ -293,16 +305,6 @@ export function pipe(
   return output;
 }
 
-type LazyFn = (value: any, index?: number, items?: any) => LazyResult<any>;
-
-type LazyOp = ((input: any) => any) & {
-  readonly lazy: ((...args: ReadonlyArray<any>) => LazyFn) & {
-    readonly indexed: boolean;
-    readonly single: boolean;
-  };
-  readonly lazyArgs?: ReadonlyArray<unknown>;
-};
-
 function _processItem(
   item: unknown,
   // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types -- Intentionally mutable, we use the accumulator directly to accumulate the results.
@@ -326,11 +328,9 @@ function _processItem(
   ) {
     // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
     const lazyFn = lazySequence[operationsIndex]!;
-    const { isIndexed, index, items } = lazyFn;
+    const { index, items } = lazyFn;
     items.push(currentItem);
-    lazyResult = isIndexed
-      ? lazyFn(currentItem, index, items)
-      : lazyFn(currentItem);
+    lazyResult = lazyFn(currentItem, index, items);
     lazyFn.index += 1;
     if (lazyResult.hasNext) {
       if (lazyResult.hasMany ?? false) {
@@ -370,7 +370,6 @@ function prepareLazyOperation(op: LazyOp): PreparedLazyOperation {
   const { lazy, lazyArgs } = op;
   const fn = lazy(...(lazyArgs ?? []));
   return Object.assign(fn, {
-    isIndexed: lazy.indexed,
     isSingle: lazy.single,
     index: 0,
     items: [] as Array<unknown>,
diff --git a/src/reduce.ts b/src/reduce.ts
index bbb2d81..3eabab2 100644
--- a/src/reduce.ts
+++ b/src/reduce.ts
@@ -1,75 +1,86 @@
 import { purry } from "./purry";
 
 /**
- * Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
+ * Executes a user-supplied "reducer" callback function on each element of the
+ * array, in order, passing in the return value from the calculation on the
+ * preceding element. The final result of running the reducer across all
+ * elements of the array is a single value. Equivalent to
+ * `Array.prototype.reduce`.
  *
- * @param items - The array to reduce.
- * @param fn - The callback function.
- * @param initialValue - The initial value to use as an accumulator value in the callback function.
+ * @param data - The items to reduce.
+ * @param callbackfn - A function to execute for each element in the array. Its
+ * return value becomes the value of the accumulator parameter on the next
+ * invocation of callbackFn. For the last invocation, the return value becomes
+ * the return value of reduce().
+ * @param initialValue - A value to which accumulator is initialized the first
+ * time the callback is called. CallbackFn starts executing with the first value
+ * in the array as currentValue.
+ * @returns The value that results from running the "reducer" callback function
+ * to completion over the entire array.
  * @signature
- *    R.reduce(items, fn, initialValue)
- *    R.reduce.indexed(items, fn, initialValue)
+ *    R.reduce(data, callbackfn, initialValue)
  * @example
  *    R.reduce([1, 2, 3, 4, 5], (acc, x) => acc + x, 100) // => 115
- *    R.reduce.indexed([1, 2, 3, 4, 5], (acc, x, i, array) => acc + x, 100) // => 115
  * @dataFirst
- * @indexed
  * @category Array
  */
-export function reduce<T, K>(
-  items: ReadonlyArray<T>,
-  fn: (acc: K, item: T) => K,
-  initialValue: K,
-): K;
+export function reduce<T, U>(
+  data: ReadonlyArray<T>,
+  callbackfn: (
+    previousValue: U,
+    currentValue: T,
+    currentIndex: number,
+    data: ReadonlyArray<T>,
+  ) => U,
+  initialValue: U,
+): U;
 
 /**
- * Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
+ * Executes a user-supplied "reducer" callback function on each element of the
+ * array, in order, passing in the return value from the calculation on the
+ * preceding element. The final result of running the reducer across all
+ * elements of the array is a single value. Equivalent to
+ * `Array.prototype.reduce`.
  *
- * @param fn - The callback function.
- * @param initialValue - The initial value to use as an accumulator value in the callback function.
+ * @param callbackfn - A function to execute for each element in the array. Its
+ * return value becomes the value of the accumulator parameter on the next
+ * invocation of callbackFn. For the last invocation, the return value becomes
+ * the return value of reduce().
+ * @param initialValue - A value to which accumulator is initialized the first
+ * time the callback is called. CallbackFn starts executing with the first value
+ * in the array as currentValue.
+ * @returns The value that results from running the "reducer" callback function
+ * to completion over the entire array.
  * @signature
  *    R.reduce(fn, initialValue)(array)
  * @example
  *    R.pipe([1, 2, 3, 4, 5], R.reduce((acc, x) => acc + x, 100)) // => 115
- *    R.pipe([1, 2, 3, 4, 5], R.reduce.indexed((acc, x, i, array) => acc + x, 100)) // => 115
  * @dataLast
- * @indexed
  * @category Array
  */
-export function reduce<T, K>(
-  fn: (acc: K, item: T) => K,
-  initialValue: K,
-): (items: ReadonlyArray<T>) => K;
+export function reduce<T, U>(
+  callbackfn: (
+    previousValue: U,
+    currentValue: T,
+    currentIndex: number,
+    data: ReadonlyArray<T>,
+  ) => U,
+  initialValue: U,
+): (data: ReadonlyArray<T>) => U;
 
 export function reduce(): unknown {
-  return purry(_reduce(false), arguments);
+  return purry(reduceImplementation, arguments);
 }
 
-const _reduce =
-  (indexed: boolean) =>
-  <T, K>(
-    items: ReadonlyArray<T>,
-    fn: (acc: K, item: T, index?: number, items?: ReadonlyArray<T>) => K,
-    initialValue: K,
-  ): K =>
-    // eslint-disable-next-line unicorn/no-array-reduce -- Our function wraps the built-in reduce.
-    items.reduce(
-      (acc, item, index) =>
-        indexed ? fn(acc, item, index, items) : fn(acc, item),
-      initialValue,
-    );
-
-export namespace reduce {
-  export function indexed<T, K>(
-    array: ReadonlyArray<T>,
-    fn: (acc: K, item: T, index: number, items: ReadonlyArray<T>) => K,
-    initialValue: K,
-  ): K;
-  export function indexed<T, K>(
-    fn: (acc: K, item: T, index: number, items: ReadonlyArray<T>) => K,
-    initialValue: K,
-  ): (array: ReadonlyArray<T>) => K;
-  export function indexed(): unknown {
-    return purry(_reduce(true), arguments);
-  }
-}
+const reduceImplementation = <T, U>(
+  data: ReadonlyArray<T>,
+  callbackfn: (
+    previousValue: U,
+    currentValue: T,
+    currentIndex: number,
+    data: ReadonlyArray<T>,
+  ) => U,
+  initialValue: U,
+): U =>
+  // eslint-disable-next-line unicorn/no-array-reduce -- Our function wraps the built-in reduce.
+  data.reduce(callbackfn, initialValue);
diff --git a/src/sortedIndexBy.ts b/src/sortedIndexBy.ts
index 45150cd..9b7abd2 100644
--- a/src/sortedIndexBy.ts
+++ b/src/sortedIndexBy.ts
@@ -3,93 +3,103 @@ import { _binarySearchCutoffIndex } from "./_binarySearchCutoffIndex";
 
 /**
  * Find the insertion position (index) of an item in an array with items sorted
- * in ascending order using a value function; so that `splice(sortedIndex, 0, item)`
- * would result in maintaining the arrays sort-ness. The array can contain
- * duplicates.
+ * in ascending order using a value function; so that
+ * `splice(sortedIndex, 0, item)` would result in maintaining the arrays sort-
+ * ness. The array can contain duplicates.
  * If the item already exists in the array the index would be of the *first*
  * occurrence of the item.
  *
  * Runs in O(logN) time.
  *
+ * See also:
+ * * `findIndex` - scans a possibly unsorted array in-order (linear search).
+ * * `sortedIndex` - like this function, but doesn't take a callbackfn.
+ * * `sortedLastIndexBy` - like this function, but finds the last suitable index.
+ * * `sortedLastIndex` - like `sortedIndex`, but finds the last suitable index.
+ * * `rankBy` - scans a possibly unsorted array in-order, returning the index based on a sorting criteria.
+ *
  * @param data - The (ascending) sorted array.
  * @param item - The item to insert.
  * @param valueFunction - All comparisons would be performed on the result of
  * calling this function on each compared item. Preferably this function should
  * return a `number` or `string`. This function should be the same as the one
- * provided to sortBy to sort the array.
+ * provided to sortBy to sort the array. The function is called exactly once on
+ * each items that is compared against in the array, and once at the beginning
+ * on `item`. When called on `item` the `index` argument is `undefined`.
  * @returns Insertion index (In the range 0..data.length).
  * @signature
  *    R.sortedIndexBy(data, item, valueFunction)
  * @example
  *    R.sortedIndexBy([{age:20},{age:22}],{age:21},prop('age')) // => 1
  * @dataFirst
- * @indexed
  * @category Array
- * @see sortedIndex, sortedIndexWith, sortedLastIndex, sortedLastIndexBy
  */
 export function sortedIndexBy<T>(
   data: ReadonlyArray<T>,
   item: T,
-  valueFunction: (item: T) => NonNullable<unknown>,
+  valueFunction: (
+    item: T,
+    index: number | undefined,
+    data: ReadonlyArray<T>,
+  ) => NonNullable<unknown>,
 ): number;
 
 /**
  * Find the insertion position (index) of an item in an array with items sorted
- * in ascending order using a value function; so that `splice(sortedIndex, 0, item)`
- * would result in maintaining the arrays sort-ness. The array can contain
- * duplicates.
+ * in ascending order using a value function; so that
+ * `splice(sortedIndex, 0, item)` would result in maintaining the arrays sort-
+ * ness. The array can contain duplicates.
  * If the item already exists in the array the index would be of the *first*
  * occurrence of the item.
  *
  * Runs in O(logN) time.
  *
+ * See also:
+ * * `findIndex` - scans a possibly unsorted array in-order (linear search).
+ * * `sortedIndex` - like this function, but doesn't take a callbackfn.
+ * * `sortedLastIndexBy` - like this function, but finds the last suitable index.
+ * * `sortedLastIndex` - like `sortedIndex`, but finds the last suitable index.
+ * * `rankBy` - scans a possibly unsorted array in-order, returning the index based on a sorting criteria.
+ *
  * @param item - The item to insert.
  * @param valueFunction - All comparisons would be performed on the result of
  * calling this function on each compared item. Preferably this function should
  * return a `number` or `string`. This function should be the same as the one
- * provided to sortBy to sort the array.
- * @returns Insertion index (In the range 0..data.length).
+ * provided to sortBy to sort the array. The function is called exactly once on
+ * each items that is compared against in the array, and once at the beginning
+ * on `item`. When called on `item` the `index` argument is `undefined`.
  * @signature
  *    R.sortedIndexBy(data, item, valueFunction)
  * @example
  *    R.sortedIndexBy([{age:20},{age:22}],{age:21},prop('age')) // => 1
  * @dataLast
- * @indexed
  * @category Array
- * @see sortedIndex, sortedIndexWith, sortedLastIndex, sortedLastIndexBy
  */
 export function sortedIndexBy<T>(
   item: T,
-  valueFunction: (item: T) => NonNullable<unknown>,
+  valueFunction: (
+    item: T,
+    index: number | undefined,
+    data: ReadonlyArray<T>,
+  ) => NonNullable<unknown>,
 ): (data: ReadonlyArray<T>) => number;
 
 export function sortedIndexBy(): unknown {
   return purry(sortedIndexByImplementation, arguments);
 }
 
-export namespace sortedIndexBy {
-  export function indexed<T>(
-    data: ReadonlyArray<T>,
-    item: T,
-    valueFunction: (item: T, index?: number) => NonNullable<unknown>,
-  ): number;
-  export function indexed<T>(
-    item: T,
-    valueFunction: (item: T, index?: number) => NonNullable<unknown>,
-  ): (data: ReadonlyArray<T>) => number;
-  export function indexed(): unknown {
-    return purry(sortedIndexByImplementation, arguments);
-  }
-}
-
 function sortedIndexByImplementation<T>(
-  array: ReadonlyArray<T>,
+  data: ReadonlyArray<T>,
   item: T,
-  valueFunction: (item: T, index?: number) => NonNullable<unknown>,
+  valueFunction: (
+    item: T,
+    index: number | undefined,
+    data: ReadonlyArray<T>,
+  ) => NonNullable<unknown>,
 ): number {
-  const value = valueFunction(item);
+  const value = valueFunction(item, undefined /* index */, data);
   return _binarySearchCutoffIndex(
-    array,
-    (pivot, index) => valueFunction(pivot, index) < value,
+    data,
+    (pivot, index) => valueFunction(pivot, index, data) < value,
   );
 }
diff --git a/src/sortedIndexWith.ts b/src/sortedIndexWith.ts
index 72140d9..eaa826b 100644
--- a/src/sortedIndexWith.ts
+++ b/src/sortedIndexWith.ts
@@ -15,6 +15,14 @@ import { _binarySearchCutoffIndex } from "./_binarySearchCutoffIndex";
  * This function is the basis for all other sortedIndex functions which search
  * for a specific item in a sorted array, and it could be used to perform
  * similar efficient searches.
+ * * `sortedIndex` - scans a sorted array with a binary search, find the first suitable index.
+ * * `sortedIndexBy` - like `sortedIndex`, but assumes sorting is based on a callbackfn.
+ * * `sortedLastIndex` - scans a sorted array with a binary search, finding the last suitable index.
+ * * `sortedLastIndexBy` - like `sortedLastIndex`, but assumes sorting is based on a callbackfn.
+ *
+ * See also:
+ * * `findIndex` - scans a possibly unsorted array in-order (linear search).
+ * * `rankBy` - scans a possibly unsorted array in-order, returning the index based on a sorting criteria.
  *
  * @param data - Array, "sorted" by `predicate`.
  * @param predicate - A predicate which also defines the array's order.
@@ -24,13 +32,12 @@ import { _binarySearchCutoffIndex } from "./_binarySearchCutoffIndex";
  * @example
  *    R.sortedIndexWith(['a','ab','abc'], (item) => item.length < 2) // => 1
  * @dataFirst
- * @indexed
  * @category Array
  * @see findIndex, sortedIndex, sortedIndexBy, sortedLastIndex, sortedLastIndexBy
  */
 export function sortedIndexWith<T>(
   data: ReadonlyArray<T>,
-  predicate: (item: T) => boolean,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
 ): number;
 
 /**
@@ -42,11 +49,19 @@ export function sortedIndexWith<T>(
  * `findIndex` which does a similar thing:
  * 1. It would run at O(logN) time instead of O(N) time.
  * 2. It always returns a value (it would return `data.length` if the
- * predicate returns `false` for all items).
+ * predicate returns `true` for all items).
  *
  * This function is the basis for all other sortedIndex functions which search
  * for a specific item in a sorted array, and it could be used to perform
  * similar efficient searches.
+ * * `sortedIndex` - scans a sorted array with a binary search, find the first suitable index.
+ * * `sortedIndexBy` - like `sortedIndex`, but assumes sorting is based on a callbackfn.
+ * * `sortedLastIndex` - scans a sorted array with a binary search, finding the last suitable index.
+ * * `sortedLastIndexBy` - like `sortedLastIndex`, but assumes sorting is based on a callbackfn.
+ *
+ * See also:
+ * * `findIndex` - scans a possibly unsorted array in-order (linear search).
+ * * `rankBy` - scans a possibly unsorted array in-order, returning the index based on a sorting criteria.
  *
  * @param predicate - A predicate which also defines the array's order.
  * @returns Index (In the range 0..data.length).
@@ -55,27 +70,12 @@ export function sortedIndexWith<T>(
  * @example
  *    R.pipe(['a','ab','abc'], R.sortedIndexWith((item) => item.length < 2)) // => 1
  * @dataLast
- * @indexed
  * @category Array
- * @see findIndex, sortedIndex, sortedIndexBy, sortedLastIndex, sortedLastIndexBy
  */
 export function sortedIndexWith<T>(
-  predicate: (item: T) => boolean,
+  predicate: (value: T, index: number, data: ReadonlyArray<T>) => boolean,
 ): (data: ReadonlyArray<T>) => number;
 
 export function sortedIndexWith(): unknown {
   return purry(_binarySearchCutoffIndex, arguments);
 }
-
-export namespace sortedIndexWith {
-  export function indexed<T>(
-    data: ReadonlyArray<T>,
-    predicate: (item: T, index: number) => NonNullable<unknown>,
-  ): number;
-  export function indexed<T>(
-    predicate: (item: T, index: number) => NonNullable<unknown>,
-  ): (data: ReadonlyArray<T>) => number;
-  export function indexed(): unknown {
-    return purry(_binarySearchCutoffIndex, arguments);
-  }
-}
diff --git a/src/sortedLastIndexBy.ts b/src/sortedLastIndexBy.ts
index 53de627..d820cb9 100644
--- a/src/sortedLastIndexBy.ts
+++ b/src/sortedLastIndexBy.ts
@@ -3,96 +3,108 @@ import { _binarySearchCutoffIndex } from "./_binarySearchCutoffIndex";
 
 /**
  * Find the insertion position (index) of an item in an array with items sorted
- * in ascending order using a value function; so that `splice(sortedIndex, 0, item)`
- * would result in maintaining the arrays sort-ness. The array can contain
- * duplicates.
+ * in ascending order using a value function; so that
+ * `splice(sortedIndex, 0, item)` would result in maintaining the arrays sort-
+ * ness. The array can contain duplicates.
  * If the item already exists in the array the index would be of the *last*
  * occurrence of the item.
  *
  * Runs in O(logN) time.
  *
+ * See also:
+ * * `findIndex` - scans a possibly unsorted array in-order (linear search).
+ * * `sortedLastIndex` - a simplified version of this function, without a callbackfn.
+ * * `sortedIndexBy` - like this function, but returns the first suitable index.
+ * * `sortedIndex` - like `sortedLastIndex` but without a callbackfn.
+ * * `rankBy` - scans a possibly unsorted array in-order, returning the index based on a sorting criteria.
+ *
  * @param data - The (ascending) sorted array.
  * @param item - The item to insert.
  * @param valueFunction - All comparisons would be performed on the result of
  * calling this function on each compared item. Preferably this function should
  * return a `number` or `string`. This function should be the same as the one
- * provided to sortBy to sort the array.
+ * provided to sortBy to sort the array. The function is called exactly once on
+ * each items that is compared against in the array, and once at the beginning
+ * on `item`. When called on `item` the `index` argument is `undefined`.
  * @returns Insertion index (In the range 0..data.length).
  * @signature
  *    R.sortedLastIndexBy(data, item, valueFunction)
  * @example
  *    R.sortedLastIndexBy([{age:20},{age:22}],{age:21},prop('age')) // => 1
  * @dataFirst
- * @indexed
  * @category Array
- * @see sortedIndex, sortedIndexBy, sortedIndexWith, sortedLastIndex
  */
 export function sortedLastIndexBy<T>(
   data: ReadonlyArray<T>,
   item: T,
-  valueFunction: (item: T) => NonNullable<unknown>,
+  valueFunction: (
+    item: T,
+    index: number | undefined,
+    data: ReadonlyArray<T>,
+  ) => NonNullable<unknown>,
 ): number;
 
 /**
  * Find the insertion position (index) of an item in an array with items sorted
- * in ascending order using a value function; so that `splice(sortedIndex, 0, item)`
- * would result in maintaining the arrays sort-ness. The array can contain
- * duplicates.
+ * in ascending order using a value function; so that
+ * `splice(sortedIndex, 0, item)` would result in maintaining the arrays sort-
+ * ness. The array can contain duplicates.
  * If the item already exists in the array the index would be of the *last*
  * occurrence of the item.
  *
  * Runs in O(logN) time.
  *
+ * See also:
+ * * `findIndex` - scans a possibly unsorted array in-order (linear search).
+ * * `sortedLastIndex` - a simplified version of this function, without a callbackfn.
+ * * `sortedIndexBy` - like this function, but returns the first suitable index.
+ * * `sortedIndex` - like `sortedLastIndex` but without a callbackfn.
+ * * `rankBy` - scans a possibly unsorted array in-order, returning the index based on a sorting criteria.
+ *
  * @param item - The item to insert.
  * @param valueFunction - All comparisons would be performed on the result of
  * calling this function on each compared item. Preferably this function should
  * return a `number` or `string`. This function should be the same as the one
- * provided to sortBy to sort the array.
+ * provided to sortBy to sort the array. The function is called exactly once on
+ * each items that is compared against in the array, and once at the beginning
+ * on `item`. When called on `item` the `index` argument is `undefined`.
  * @returns Insertion index (In the range 0..data.length).
  * @signature
  *    R.sortedLastIndexBy(item, valueFunction)(data)
  * @example
  *    R.pipe([{age:20},{age:22}],sortedLastIndexBy({age:21},prop('age'))) // => 1
  * @dataLast
- * @indexed
  * @category Array
  * @see sortedIndex, sortedIndexBy, sortedIndexWith, sortedLastIndex
  */
 export function sortedLastIndexBy<T>(
   item: T,
-  valueFunction: (item: T) => NonNullable<unknown>,
+  valueFunction: (
+    item: T,
+    index: number | undefined,
+    data: ReadonlyArray<T>,
+  ) => NonNullable<unknown>,
 ): (data: ReadonlyArray<T>) => number;
 
 export function sortedLastIndexBy(): unknown {
   return purry(sortedLastIndexByImplementation, arguments);
 }
 
-export namespace sortedLastIndexBy {
-  export function indexed<T>(
-    data: ReadonlyArray<T>,
-    item: T,
-    valueFunction: (item: T, index?: number) => NonNullable<unknown>,
-  ): number;
-  export function indexed<T>(
-    item: T,
-    valueFunction: (item: T, index?: number) => NonNullable<unknown>,
-  ): (data: ReadonlyArray<T>) => number;
-  export function indexed(): unknown {
-    return purry(sortedLastIndexByImplementation, arguments);
-  }
-}
-
 function sortedLastIndexByImplementation<T>(
   array: ReadonlyArray<T>,
   item: T,
-  valueFunction: (item: T, index?: number) => NonNullable<unknown>,
+  valueFunction: (
+    item: T,
+    index: number | undefined,
+    data: ReadonlyArray<T>,
+  ) => NonNullable<unknown>,
 ): number {
-  const value = valueFunction(item);
+  const value = valueFunction(item, undefined /* index */, array);
   return _binarySearchCutoffIndex(
     array,
     // The only difference between the regular implementation and the "last"
     // variation is that we consider the pivot with equality too, so that we
     // skip all equal values in addition to the lower ones.
-    (pivot, index) => valueFunction(pivot, index) <= value,
+    (pivot, index) => valueFunction(pivot, index, array) <= value,
   );
 }
diff --git a/src/sumBy.ts b/src/sumBy.ts
index 2d3f890..1283e9d 100644
--- a/src/sumBy.ts
+++ b/src/sumBy.ts
@@ -1,78 +1,59 @@
 import { purry } from "./purry";
-import type { PredIndexed, PredIndexedOptional } from "./_types";
-
-const _sumBy =
-  (indexed: boolean) =>
-  <T>(array: ReadonlyArray<T>, fn: PredIndexedOptional<T, number>) => {
-    let sum = 0;
-    for (let index = 0; index < array.length; index++) {
-      // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
-      const item = array[index]!;
-      const summand = indexed ? fn(item, index, array) : fn(item);
-      sum += summand;
-    }
-    return sum;
-  };
 
 /**
  * Returns the sum of the elements of an array using the provided predicate.
  *
- * @param fn - Predicate function.
+ * @param callbackfn - Predicate function.
  * @signature
  *   R.sumBy(fn)(array)
- *   R.sumBy.indexed(fn)(array)
  * @example
  *    R.pipe(
  *      [{a: 5}, {a: 1}, {a: 3}],
  *      R.sumBy(x => x.a)
  *    ) // 9
  * @dataLast
- * @indexed
  * @category Array
  */
 
 export function sumBy<T>(
-  fn: (item: T) => number,
+  callbackfn: (value: T, index: number, data: ReadonlyArray<T>) => number,
 ): (items: ReadonlyArray<T>) => number;
 
 /**
  * Returns the sum of the elements of an array using the provided predicate.
  *
- * @param items - The array.
- * @param fn - Predicate function.
+ * @param data - The array.
+ * @param callbackfn - Predicate function.
  * @signature
  *   R.sumBy(array, fn)
- *   R.sumBy.indexed(array, fn)
  * @example
  *    R.sumBy(
  *      [{a: 5}, {a: 1}, {a: 3}],
  *      x => x.a
  *    ) // 9
  * @dataFirst
- * @indexed
  * @category Array
  */
 
 export function sumBy<T>(
-  items: ReadonlyArray<T>,
-  fn: (item: T) => number,
+  data: ReadonlyArray<T>,
+  callbackfn: (value: T, index: number, data: ReadonlyArray<T>) => number,
 ): number;
 
 export function sumBy(): unknown {
-  return purry(_sumBy(false), arguments);
+  return purry(sumByImplementation, arguments);
 }
 
-export namespace sumBy {
-  export function indexed<T>(
-    array: ReadonlyArray<T>,
-    fn: PredIndexed<T, number>,
-  ): number;
-
-  export function indexed<T>(
-    fn: PredIndexed<T, number>,
-  ): (array: ReadonlyArray<T>) => number;
-
-  export function indexed(): unknown {
-    return purry(_sumBy(true), arguments);
+const sumByImplementation = <T>(
+  array: ReadonlyArray<T>,
+  callbackfn: (value: T, index: number, data: ReadonlyArray<T>) => number,
+): number => {
+  let sum = 0;
+  for (let index = 0; index < array.length; index++) {
+    // TODO: Once we bump our Typescript target above ES5 we can use Array.prototype.entries to iterate over both the index and the value.
+    const item = array[index]!;
+    const summand = callbackfn(item, index, array);
+    sum += summand;
   }
-}
+  return sum;
+};
diff --git a/src/uniqueWith.ts b/src/uniqueWith.ts
index a7cd558..96f30a3 100644
--- a/src/uniqueWith.ts
+++ b/src/uniqueWith.ts
@@ -1,5 +1,4 @@
 import { _reduceLazy } from "./_reduceLazy";
-import { _toLazyIndexed } from "./_toLazyIndexed";
 import type { LazyEvaluator } from "./pipe";
 import { purry } from "./purry";
 
@@ -9,7 +8,7 @@ type IsEquals<T> = (a: T, b: T) => boolean;
  * Returns a new array containing only one copy of each element in the original
  * list. Elements are compared by custom comparator isEquals.
  *
- * @param array - The array to filter.
+ * @param data - The array to filter.
  * @param isEquals - The comparator.
  * @signature
  *    R.uniqueWith(array, isEquals)
@@ -22,7 +21,7 @@ type IsEquals<T> = (a: T, b: T) => boolean;
  * @category Array
  */
 export function uniqueWith<T>(
-  array: ReadonlyArray<T>,
+  data: ReadonlyArray<T>,
   isEquals: IsEquals<T>,
 ): Array<T>;
 
@@ -46,28 +45,20 @@ export function uniqueWith<T>(
  */
 export function uniqueWith<T>(
   isEquals: IsEquals<T>,
-): (array: ReadonlyArray<T>) => Array<T>;
+): (data: ReadonlyArray<T>) => Array<T>;
 
 export function uniqueWith(): unknown {
-  return purry(uniqueWithImplementation, arguments, uniqueWith.lazy);
+  return purry(uniqueWithImplementation, arguments, lazyImplementation);
 }
 
-function uniqueWithImplementation<T>(
-  array: ReadonlyArray<T>,
+const uniqueWithImplementation = <T>(
+  data: ReadonlyArray<T>,
   isEquals: IsEquals<T>,
-): Array<T> {
-  const lazy = uniqueWith.lazy(isEquals);
-  return _reduceLazy(array, lazy, true);
-}
+): Array<T> => _reduceLazy(data, lazyImplementation(isEquals));
 
-const _lazy =
+const lazyImplementation =
   <T>(isEquals: IsEquals<T>): LazyEvaluator<T> =>
-  (value, index, array) =>
-    array !== undefined &&
-    array.findIndex((otherValue) => isEquals(value, otherValue)) === index
+  (value, index, data) =>
+    data.findIndex((otherValue) => isEquals(value, otherValue)) === index
       ? { done: false, hasNext: true, next: value }
       : { done: false, hasNext: false };
-
-export namespace uniqueWith {
-  export const lazy = _toLazyIndexed(_lazy);
-}


Test patch:
diff --git a/src/filter.test.ts b/src/filter.test.ts
index d209075..98125ac 100644
--- a/src/filter.test.ts
+++ b/src/filter.test.ts
@@ -1,99 +1,107 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
 import { filter } from "./filter";
+import { identity } from "./identity";
+import { map } from "./map";
 import { pipe } from "./pipe";
 
-function isNumber<T>(data: T): data is Extract<T, number> {
-  return typeof data === "number";
-} // TODO Refactor to remeda function
+describe("runtime", () => {
+  describe("data_first", () => {
+    it("filter", () => {
+      const result = filter([1, 2, 3], (x) => x % 2 === 1);
+      expect(result).toEqual([1, 3]);
+    });
 
-describe("data_first", () => {
-  it("filter", () => {
-    const result = filter([1, 2, 3] as const, (x) => x % 2 === 1);
-    assertType<Array<1 | 2 | 3>>(result); // Type test
-    expect(result).toEqual([1, 3]);
-  });
+    it("data_first with typescript guard", () => {
+      const result = filter([1, 2, 3, "abc", true], isNumber);
+      expect(result).toEqual([1, 2, 3]);
+    });
 
-  it("data_first with typescript guard", () => {
-    const result = filter([1, 2, 3, "abc", true] as const, isNumber);
-    assertType<Array<1 | 2 | 3>>(result); // Type test
-    expect(result).toEqual([1, 2, 3]);
+    it("filter indexed", () => {
+      const result = filter([1, 2, 3], (x, i) => x % 2 === 1 && i !== 1);
+      expect(result).toEqual([1, 3]);
+    });
   });
 
-  it("filter.indexed", () => {
-    const result = filter.indexed(
-      [1, 2, 3] as const,
-      (x, i) => x % 2 === 1 && i !== 1,
-    );
-    assertType<Array<1 | 2 | 3>>(result); // Type test
-    expect(result).toEqual([1, 3]);
-  });
+  describe("data_last", () => {
+    it("filter", () => {
+      const counter = vi.fn(identity);
+      const result = pipe(
+        [1, 2, 3],
+        filter((x) => x % 2 === 1),
+        map(counter),
+      );
+      expect(counter).toHaveBeenCalledTimes(2);
+      expect(result).toEqual([1, 3]);
+    });
+
+    it("filter with typescript guard", () => {
+      const counter = vi.fn(identity);
+      const result = pipe(
+        [1, 2, 3, false, "text"],
+        filter(isNumber),
+        map(counter),
+      );
+      expect(counter).toHaveBeenCalledTimes(3);
+      expect(result).toEqual([1, 2, 3]);
+    });
 
-  it("filter.indexed with typescript guard", () => {
-    const result = filter.indexed(
-      [1, 2, 3, false, "text"] as const, // Type (1 | 2 | 3 | false | "text")[]
-      isNumber,
-    );
-    assertType<Array<1 | 2 | 3>>(result); // Type test (1 | 2 | 3)[]
-    expect(result).toEqual([1, 2, 3]);
+    it("filter indexed", () => {
+      const counter = vi.fn(identity);
+      const result = pipe(
+        [1, 2, 3],
+        filter((x, i) => x % 2 === 1 && i !== 1),
+        map(counter),
+      );
+      expect(counter).toHaveBeenCalledTimes(2);
+      expect(result).toEqual([1, 3]);
+    });
   });
 });
 
-describe("data_last", () => {
-  it("filter", () => {
-    const counter = createLazyInvocationCounter();
-    const result = pipe(
-      [1, 2, 3] as const,
-      filter((x) => x % 2 === 1),
-      counter.fn(),
-    );
-    expect(counter.count).toHaveBeenCalledTimes(2);
-    assertType<Array<1 | 2 | 3>>(result); // Type test (1 | 2 | 3)[]
-    expect(result).toEqual([1, 3]);
-  });
+describe("typing", () => {
+  describe("data_first", () => {
+    it("filter", () => {
+      const result = filter([1, 2, 3] as const, (x) => x % 2 === 1);
+      expectTypeOf(result).toEqualTypeOf<Array<1 | 2 | 3>>();
+    });
 
-  it("filter", () => {
-    const counter = createLazyInvocationCounter();
-    const result = pipe(
-      [1, 2, 3] as const,
-      filter((x) => x % 2 === 1),
-      counter.fn(),
-    );
-    expect(counter.count).toHaveBeenCalledTimes(2);
-    assertType<Array<1 | 2 | 3>>(result); // Type test (1 | 2 | 3)[]
-    expect(result).toEqual([1, 3]);
-  });
+    it("data_first with typescript guard", () => {
+      const result = filter([1, 2, 3, "abc", true] as const, isNumber);
+      expectTypeOf(result).toEqualTypeOf<Array<1 | 2 | 3>>();
+    });
 
-  it("filter with typescript guard", () => {
-    const counter = createLazyInvocationCounter();
-    const result = pipe(
-      [1, 2, 3, false, "text"] as const, // Type (1 | 2 | 3 | false | "text")[]
-      filter(isNumber),
-      counter.fn(),
-    );
-    expect(counter.count).toHaveBeenCalledTimes(3);
-    assertType<Array<1 | 2 | 3>>(result); // Type test (1 | 2 | 3)[]
-    expect(result).toEqual([1, 2, 3]);
+    it("filter indexed", () => {
+      const result = filter(
+        [1, 2, 3] as const,
+        (x, i) => x % 2 === 1 && i !== 1,
+      );
+      expectTypeOf(result).toEqualTypeOf<Array<1 | 2 | 3>>();
+    });
   });
-  it("filter.indexed with typescript guard", () => {
-    const counter = createLazyInvocationCounter();
-    const result = pipe(
-      [1, 2, 3, false, "text"] as const, // Type (1 | 2 | 3 | false | "text")[]
-      filter.indexed(isNumber),
-      counter.fn(),
-    );
-    assertType<Array<1 | 2 | 3>>(result); // Type test (1 | 2 | 3)[]
-    expect(counter.count).toHaveBeenCalledTimes(3);
-    expect(result).toEqual([1, 2, 3]);
-  });
-  it("filter.indexed", () => {
-    const counter = createLazyInvocationCounter();
-    const result = pipe(
-      [1, 2, 3] as const,
-      filter.indexed((x, i) => x % 2 === 1 && i !== 1),
-      counter.fn(),
-    );
-    assertType<Array<1 | 2 | 3>>(result); // Type test (1 | 2 | 3)[]
-    expect(counter.count).toHaveBeenCalledTimes(2);
-    expect(result).toEqual([1, 3]);
+
+  describe("data_last", () => {
+    it("filter", () => {
+      const result = pipe(
+        [1, 2, 3] as const,
+        filter((x) => x % 2 === 1),
+      );
+      expectTypeOf(result).toEqualTypeOf<Array<1 | 2 | 3>>();
+    });
+
+    it("filter with typescript guard", () => {
+      const result = pipe([1, 2, 3, false, "text"] as const, filter(isNumber));
+      expectTypeOf(result).toEqualTypeOf<Array<1 | 2 | 3>>();
+    });
+
+    it("filter indexed", () => {
+      const result = pipe(
+        [1, 2, 3] as const,
+        filter((x, i) => x % 2 === 1 && i !== 1),
+      );
+      expectTypeOf(result).toEqualTypeOf<Array<1 | 2 | 3>>();
+    });
   });
 });
+
+// TODO: The Remeda `isNumber` utility isn't narrowing our types correctly for
+// our tests here.
+const isNumber = <T>(x: T): x is Extract<T, number> => typeof x === "number";
diff --git a/src/find.test.ts b/src/find.test.ts
index ad7b29f..7cd75f7 100644
--- a/src/find.test.ts
+++ b/src/find.test.ts
@@ -1,46 +1,84 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
 import { find } from "./find";
+import { isString } from "./isString";
+import { map } from "./map";
 import { pipe } from "./pipe";
 
-const array = [
-  { a: 1, b: 1 },
-  { a: 1, b: 2 },
-  { a: 2, b: 1 },
-  { a: 1, b: 3 },
-] as const;
-const expected = { a: 1, b: 2 };
-
-describe("data first", () => {
-  test("find", () => {
-    expect(find(array, (x) => x.b === 2)).toEqual(expected);
-  });
-  test("find.indexed", () => {
-    expect(find.indexed(array, (x, idx) => x.b === 2 && idx === 1)).toEqual(
-      expected,
-    );
+describe("runtime", () => {
+  describe("data first", () => {
+    test("find", () => {
+      expect(
+        find(
+          [
+            { a: 1, b: 1 },
+            { a: 1, b: 2 },
+            { a: 2, b: 1 },
+            { a: 1, b: 3 },
+          ],
+          ({ b }) => b === 2,
+        ),
+      ).toEqual({ a: 1, b: 2 });
+    });
+
+    test("indexed ", () => {
+      expect(
+        find(
+          [
+            { a: 1, b: 1 },
+            { a: 1, b: 2 },
+            { a: 2, b: 1 },
+            { a: 1, b: 3 },
+          ],
+          ({ b }, idx) => b === 2 && idx === 1,
+        ),
+      ).toEqual({ a: 1, b: 2 });
+    });
   });
-});
 
-describe("data last", () => {
-  test("find", () => {
-    const counter = createLazyInvocationCounter();
-    const actual = pipe(
-      array,
-      counter.fn(),
-      find((x) => x.b === 2),
-    );
-    expect(counter.count).toHaveBeenCalledTimes(2);
-    expect(actual).toEqual(expected);
+  describe("data last", () => {
+    test("find", () => {
+      const counter = vi.fn(
+        (x: { readonly a: number; readonly b: number }) => x,
+      );
+
+      const actual = pipe(
+        [
+          { a: 1, b: 1 },
+          { a: 1, b: 2 },
+          { a: 2, b: 1 },
+          { a: 1, b: 3 },
+        ],
+        map(counter),
+        find(({ b }) => b === 2),
+      );
+
+      expect(counter).toHaveBeenCalledTimes(2);
+      expect(actual).toEqual({ a: 1, b: 2 });
+    });
+
+    test("indexed", () => {
+      const counter = vi.fn(
+        (x: { readonly a: number; readonly b: number }) => x,
+      );
+
+      const actual = pipe(
+        [
+          { a: 1, b: 1 },
+          { a: 1, b: 2 },
+          { a: 2, b: 1 },
+          { a: 1, b: 3 },
+        ],
+        map(counter),
+        find(({ b }, idx) => b === 2 && idx === 1),
+      );
+      expect(counter).toHaveBeenCalledTimes(2);
+      expect(actual).toEqual({ a: 1, b: 2 });
+    });
   });
+});
 
-  test("find.indexed", () => {
-    const counter = createLazyInvocationCounter();
-    const actual = pipe(
-      array,
-      counter.fn(),
-      find.indexed((x, idx) => x.b === 2 && idx === 1),
-    );
-    expect(counter.count).toHaveBeenCalledTimes(2);
-    expect(actual).toEqual(expected);
+describe("typing", () => {
+  test("can narrow types", () => {
+    const result = find([1, "a"], isString);
+    expectTypeOf(result).toEqualTypeOf<string | undefined>();
   });
 });
diff --git a/src/findIndex.test.ts b/src/findIndex.test.ts
index ab25329..6fc2770 100644
--- a/src/findIndex.test.ts
+++ b/src/findIndex.test.ts
@@ -1,41 +1,32 @@
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
 import { findIndex } from "./findIndex";
 import { pipe } from "./pipe";
 
 describe("data first", () => {
-  test("findIndex", () => {
-    expect(findIndex([10, 20, 30] as const, (x) => x === 20)).toBe(1);
+  test("found", () => {
+    expect(findIndex([10, 20, 30], (x) => x === 20)).toBe(1);
   });
 
-  test("findIndex.indexed", () => {
-    expect(findIndex([10, 20, 30] as const, (x) => x === 20)).toBe(1);
-  });
-
-  test("findIndex -1", () => {
-    expect(findIndex([2, 3, 4] as const, (x) => x === (20 as number))).toBe(-1);
+  test("not found", () => {
+    expect(findIndex([2, 3, 4], (x) => x === 20)).toBe(-1);
   });
 });
 
 describe("data last", () => {
-  test("findIndex", () => {
-    const counter = createLazyInvocationCounter();
-    const actual = pipe(
-      [10, 20, 30] as const,
-      counter.fn(),
-      findIndex((x) => x === 20),
-    );
-    expect(counter.count).toHaveBeenCalledTimes(2);
-    expect(actual).toEqual(1);
+  test("found", () => {
+    expect(
+      pipe(
+        [10, 20, 30],
+        findIndex((x) => x === 20),
+      ),
+    ).toEqual(1);
   });
 
-  test("findIndex.indexed", () => {
-    const counter = createLazyInvocationCounter();
-    const actual = pipe(
-      [10, 20, 30] as const,
-      counter.fn(),
-      findIndex.indexed((x) => x === 20),
-    );
-    expect(counter.count).toHaveBeenCalledTimes(2);
-    expect(actual).toEqual(1);
+  test("not found", () => {
+    expect(
+      pipe(
+        [2, 3, 4],
+        findIndex((x) => x === 20),
+      ),
+    ).toEqual(-1);
   });
 });
diff --git a/src/findLast.test.ts b/src/findLast.test.ts
index edfe31c..8da3a68 100644
--- a/src/findLast.test.ts
+++ b/src/findLast.test.ts
@@ -1,37 +1,37 @@
 import { findLast } from "./findLast";
 import { pipe } from "./pipe";
 
-const array = [1, 2, 3, 4];
-
 describe("data first", () => {
   test("findLast", () => {
-    expect(findLast(array, (x) => x % 2 === 1)).toEqual(3);
+    expect(findLast([1, 2, 3, 4], (x) => x % 2 === 1)).toEqual(3);
   });
-  test("findLast.indexed", () => {
-    expect(findLast.indexed(array, (x, i) => x === 3 && i === 2)).toEqual(3);
+  test("indexed", () => {
+    expect(findLast([1, 2, 3, 4], (x, i) => x === 3 && i === 2)).toEqual(3);
   });
   test("findLast first value", () => {
-    expect(findLast(array, (x) => x === 1)).toEqual(1);
+    expect(findLast([1, 2, 3, 4], (x) => x === 1)).toEqual(1);
   });
   test("findLast no match", () => {
-    expect(findLast(array, (x) => x === 5)).toBeUndefined();
+    expect(findLast([1, 2, 3, 4], (x) => x === 5)).toBeUndefined();
   });
 });
 
 describe("data last", () => {
   test("findLast", () => {
-    const actual = pipe(
-      [1, 2, 3, 4],
-      findLast((x) => x % 2 === 1),
-    );
-    expect(actual).toEqual(3);
+    expect(
+      pipe(
+        [1, 2, 3, 4],
+        findLast((x) => x % 2 === 1),
+      ),
+    ).toEqual(3);
   });
 
-  test("findLast.indexed", () => {
-    const actual = pipe(
-      [1, 2, 3, 4],
-      findLast.indexed((x, i) => x === 3 && i === 2),
-    );
-    expect(actual).toEqual(3);
+  test("indexed", () => {
+    expect(
+      pipe(
+        [1, 2, 3, 4],
+        findLast((x, i) => x === 3 && i === 2),
+      ),
+    ).toEqual(3);
   });
 });
diff --git a/src/findLastIndex.test.ts b/src/findLastIndex.test.ts
index bcbe971..22fc45c 100644
--- a/src/findLastIndex.test.ts
+++ b/src/findLastIndex.test.ts
@@ -1,42 +1,36 @@
 import { findLastIndex } from "./findLastIndex";
 import { pipe } from "./pipe";
 
-const array = [1, 2, 3, 4];
-
 describe("data first", () => {
   test("findLastIndex", () => {
-    expect(findLastIndex(array, (x) => x % 2 === 1)).toBe(2);
-  });
-
-  test("findLastIndex.indexed", () => {
-    expect(findLastIndex.indexed(array, (x) => x % 2 === 1)).toBe(2);
+    expect(findLastIndex([1, 2, 3, 4], (x) => x % 2 === 1)).toBe(2);
   });
 
   test("findLast first value", () => {
-    expect(findLastIndex(array, (x) => x === 1)).toEqual(0);
+    expect(findLastIndex([1, 2, 3, 4], (x) => x === 1)).toEqual(0);
   });
 
   test("findLastIndex -1", () => {
-    expect(findLastIndex(array, (x) => x === 5)).toBe(-1);
+    expect(findLastIndex([1, 2, 3, 4], (x) => x === 5)).toBe(-1);
   });
 });
 
 describe("data last", () => {
-  test("findLastIndex", () => {
+  test("found", () => {
     expect(
       pipe(
-        array,
+        [1, 2, 3, 4],
         findLastIndex((x) => x % 2 === 1),
       ),
     ).toEqual(2);
   });
 
-  test("findLastIndex.indexed", () => {
+  test("not found", () => {
     expect(
       pipe(
-        array,
-        findLastIndex.indexed((x) => x % 2 === 1),
+        [1, 2, 3, 4],
+        findLastIndex((x) => x === 5),
       ),
-    ).toEqual(2);
+    ).toBe(-1);
   });
 });
diff --git a/src/forEach.test.ts b/src/forEach.test.ts
index 3216e77..8d67a2c 100644
--- a/src/forEach.test.ts
+++ b/src/forEach.test.ts
@@ -2,65 +2,52 @@ import { forEach } from "./forEach";
 import { pipe } from "./pipe";
 import { take } from "./take";
 
-const array = [1, 2, 3] as const;
-
-describe("data_first", () => {
-  it("forEach", () => {
-    const cb = vi.fn();
-    const result = forEach(array, cb);
-    expect(cb.mock.calls).toEqual([[1], [2], [3]]);
-    expect(result).toEqual(array);
-  });
-  it("forEach.indexed", () => {
+describe("runtime", () => {
+  test("dataFirst", () => {
+    const data = [1, 2, 3];
     const cb = vi.fn();
-    const result = forEach.indexed(array, cb);
-    expect(cb.mock.calls).toEqual([
-      [1, 0, array],
-      [2, 1, array],
-      [3, 2, array],
-    ]);
-    expect(result).toEqual(array);
+
+    forEach(data, cb);
+
+    expect(cb).toHaveBeenCalledWith(1, 0, data);
+    expect(cb).toHaveBeenCalledWith(2, 1, data);
+    expect(cb).toHaveBeenCalledWith(3, 2, data);
   });
-});
 
-describe("data_last", () => {
-  it("forEach", () => {
+  test("dataLast", () => {
+    const data = [1, 2, 3];
     const cb = vi.fn();
-    const result = pipe(array, forEach(cb));
-    expect(cb.mock.calls).toEqual([[1], [2], [3]]);
-    expect(result).toEqual(array);
+
+    const result = forEach(cb)(data);
+
+    expect(cb).toHaveBeenCalledWith(1, 0, data);
+    expect(cb).toHaveBeenCalledWith(2, 1, data);
+    expect(cb).toHaveBeenCalledWith(3, 2, data);
+
+    // dataLast used directly, we return the same reference.
+    expect(result).toBe(data);
   });
-  it("forEach.indexed", () => {
+
+  test("pipe", () => {
+    const data = [1, 2, 3];
     const cb = vi.fn();
-    const result = pipe(array, forEach.indexed(cb));
-    expect(cb.mock.calls).toEqual([
-      [1, 0, array],
-      [2, 1, array],
-      [3, 2, array],
-    ]);
-    expect(result).toEqual(array);
-  });
-});
 
-describe("pipe", () => {
-  it("with take", () => {
-    const count = vi.fn();
-    const result = pipe(
-      [1, 2, 3],
-      forEach(() => {
-        count();
-      }),
-      take(2),
-    );
-    expect(count).toHaveBeenCalledTimes(2);
-    expect(result).toEqual([1, 2]);
+    const result = pipe(data, forEach(cb));
+
+    expect(cb).toHaveBeenCalledWith(1, 0, data);
+    expect(cb).toHaveBeenCalledWith(2, 1, data);
+    expect(cb).toHaveBeenCalledWith(3, 2, data);
+
+    expect(result).toStrictEqual(data);
+    // The pipe reconstructs the array.
+    expect(result).not.toBe(data);
   });
 
-  it("indexed", () => {
+  test("with take", () => {
     const count = vi.fn();
     const result = pipe(
       [1, 2, 3],
-      forEach.indexed(() => {
+      forEach(() => {
         count();
       }),
       take(2),
diff --git a/src/forEachObj.test.ts b/src/forEachObj.test.ts
index f95556e..727f5d0 100644
--- a/src/forEachObj.test.ts
+++ b/src/forEachObj.test.ts
@@ -1,48 +1,33 @@
 import { forEachObj } from "./forEachObj";
 import { pipe } from "./pipe";
 
-const obj = {
-  a: 1,
-  b: 2,
-  c: 3,
-};
-
-describe("data_first", () => {
-  it("forEachObj", () => {
+describe("runtime", () => {
+  test("dataFirst", () => {
+    const data = {
+      a: 1,
+      b: 2,
+      c: 3,
+    };
     const cb = vi.fn();
-    const result = forEachObj(obj, cb);
-    expect(cb.mock.calls).toEqual([[1], [2], [3]]);
-    expect(result).toEqual(obj);
-  });
 
-  it("forEachObj.indexed", () => {
-    const cb = vi.fn();
-    const result = forEachObj.indexed(obj, cb);
-    expect(cb.mock.calls).toEqual([
-      [1, "a", obj],
-      [2, "b", obj],
-      [3, "c", obj],
-    ]);
-    expect(result).toEqual(obj);
-  });
-});
+    forEachObj(data, cb);
 
-describe("data_last", () => {
-  it("forEachObj", () => {
-    const cb = vi.fn();
-    const result = pipe(obj, forEachObj(cb));
-    expect(cb.mock.calls).toEqual([[1], [2], [3]]);
-    expect(result).toEqual(obj);
+    expect(cb).toHaveBeenCalledWith(1, "a", data);
+    expect(cb).toHaveBeenCalledWith(2, "b", data);
+    expect(cb).toHaveBeenCalledWith(3, "c", data);
   });
 
-  it("forEachObj.indexed", () => {
-    const cb = vi.fn<[number, string, typeof obj]>();
-    const result = pipe(obj, forEachObj.indexed(cb));
-    expect(cb.mock.calls).toEqual([
-      [1, "a", obj],
-      [2, "b", obj],
-      [3, "c", obj],
-    ]);
-    expect(result).toEqual(obj);
+  test("dataLast", () => {
+    const data = {
+      a: 1,
+      b: 2,
+      c: 3,
+    };
+    const cb = vi.fn<[number, string, typeof data]>();
+
+    expect(pipe(data, forEachObj(cb))).toBe(data);
+    expect(cb).toHaveBeenCalledWith(1, "a", data);
+    expect(cb).toHaveBeenCalledWith(2, "b", data);
+    expect(cb).toHaveBeenCalledWith(3, "c", data);
   });
 });
diff --git a/src/groupBy.test.ts b/src/groupBy.test.ts
index 5bd4142..94a80f9 100644
--- a/src/groupBy.test.ts
+++ b/src/groupBy.test.ts
@@ -25,27 +25,6 @@ describe("runtime", () => {
         2: [{ a: 2, b: 1 }],
       });
     });
-
-    test("groupBy.indexed", () => {
-      expect(
-        groupBy.indexed(
-          [
-            { a: 1, b: 1 },
-            { a: 1, b: 2 },
-            { a: 2, b: 1 },
-            { a: 1, b: 3 },
-          ],
-          prop("a"),
-        ),
-      ).toEqual({
-        1: [
-          { a: 1, b: 1 },
-          { a: 1, b: 2 },
-          { a: 1, b: 3 },
-        ],
-        2: [{ a: 2, b: 1 }],
-      });
-    });
   });
 
   describe("data last", () => {
@@ -69,27 +48,6 @@ describe("runtime", () => {
         2: [{ a: 2, b: 1 }],
       });
     });
-
-    test("groupBy.indexed", () => {
-      expect(
-        pipe(
-          [
-            { a: 1, b: 1 },
-            { a: 1, b: 2 },
-            { a: 2, b: 1 },
-            { a: 1, b: 3 },
-          ],
-          groupBy.indexed(prop("a")),
-        ),
-      ).toEqual({
-        1: [
-          { a: 1, b: 1 },
-          { a: 1, b: 2 },
-          { a: 1, b: 3 },
-        ],
-        2: [{ a: 2, b: 1 }],
-      });
-    });
   });
 
   describe("Filtering on undefined grouper result", () => {
@@ -107,7 +65,7 @@ describe("runtime", () => {
     });
 
     test("regular indexed", () => {
-      const result = groupBy.indexed(
+      const result = groupBy(
         ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"],
         (_, index) => (index % 2 === 0 ? "even" : undefined),
       );
@@ -227,7 +185,7 @@ describe("typing", () => {
     });
 
     test("indexed", () => {
-      const { even, ...rest } = groupBy.indexed(
+      const { even, ...rest } = groupBy(
         ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"],
         (_, index) => (index % 2 === 0 ? "even" : undefined),
       );
diff --git a/src/map.test.ts b/src/map.test.ts
index 387a1ed..e54f5dc 100644
--- a/src/map.test.ts
+++ b/src/map.test.ts
@@ -13,8 +13,8 @@ describe("runtime", () => {
       expect(map([1, 2, 3], multiply(2))).toEqual([2, 4, 6]);
     });
 
-    it("map.indexed", () => {
-      expect(map.indexed([0, 0, 0], (_, i) => i)).toEqual([0, 1, 2]);
+    it("map indexed", () => {
+      expect(map([0, 0, 0], (_, i) => i)).toEqual([0, 1, 2]);
     });
   });
 
@@ -23,11 +23,11 @@ describe("runtime", () => {
       expect(pipe([1, 2, 3], map(multiply(2)))).toEqual([2, 4, 6]);
     });
 
-    it("map.indexed", () => {
+    it("map", () => {
       expect(
         pipe(
           [0, 0, 0],
-          map.indexed((_, i) => i),
+          map((_, i) => i),
         ),
       ).toEqual([0, 1, 2]);
     });
@@ -45,9 +45,7 @@ describe("runtime", () => {
     it("invoked lazily (indexed)", () => {
       const count = vi.fn((_: unknown, index: number) => index);
 
-      expect(pipe([0, 0, 0] as const, map.indexed(count), take(2))).toEqual([
-        0, 1,
-      ]);
+      expect(pipe([0, 0, 0], map(count), take(2))).toEqual([0, 1]);
 
       expect(count).toHaveBeenCalledTimes(2);
     });
@@ -61,13 +59,13 @@ describe("runtime", () => {
       expect(
         pipe(
           [1, 2, 3, 4, 5],
-          map.indexed((x, i, items) => {
+          map((x, i, items) => {
             anyItems1.push([...items]);
             indexes1.push(i);
             return x;
           }),
           filter((x) => x % 2 === 1),
-          map.indexed((x, i, items) => {
+          map((x, i, items) => {
             anyItems2.push([...items]);
             indexes2.push(i);
             return x;
@@ -112,29 +110,11 @@ describe("runtime", () => {
 
   describe("Indexed", () => {
     it("number array", () => {
-      expect(map.indexed([1, 2, 3], (x, index) => x + index)).toEqual([
-        1, 3, 5,
-      ]);
+      expect(map([1, 2, 3], (x, index) => x + index)).toEqual([1, 3, 5]);
     });
 
     it("mixed type tuple", () => {
-      expect(map.indexed([1, "2", true], (_, index) => index)).toEqual([
-        0, 1, 2,
-      ]);
-    });
-
-    it("complex variadic number array", () => {
-      const input = [
-        "hello",
-        "world",
-        1,
-        "testing",
-        "testing",
-        "testing",
-        123,
-        true,
-      ];
-      expect(map.indexed(input, identity)).toEqual(input);
+      expect(map([1, "2", true], (_, index) => index)).toEqual([0, 1, 2]);
     });
   });
 });
@@ -233,15 +213,12 @@ describe("typing", () => {
 
   describe("Indexed", () => {
     it("number array", () => {
-      const result = map.indexed(
-        [1, 2, 3] as Array<number>,
-        (x, index) => x + index,
-      );
+      const result = map([1, 2, 3] as Array<number>, (x, index) => x + index);
       expectTypeOf(result).toEqualTypeOf<Array<number>>();
     });
 
     it("readonly number array", () => {
-      const result = map.indexed(
+      const result = map(
         [1, 2, 3] as ReadonlyArray<number>,
         (x, index) => x + index,
       );
@@ -249,7 +226,7 @@ describe("typing", () => {
     });
 
     it("number 3-tuple", () => {
-      const result = map.indexed(
+      const result = map(
         [1, 2, 3] as [number, number, number],
         (x, index) => x + index,
       );
@@ -257,7 +234,7 @@ describe("typing", () => {
     });
 
     it("readonly number 3-tuple", () => {
-      const result = map.indexed(
+      const result = map(
         [1, 2, 3] as readonly [number, number, number],
         (x, index) => x + index,
       );
@@ -265,7 +242,7 @@ describe("typing", () => {
     });
 
     it("named number 3-tuple", () => {
-      const result = map.indexed(
+      const result = map(
         [1, 2, 3] as [item1: number, item2: number, item3: number],
         (x, index) => x + index,
       );
@@ -277,7 +254,7 @@ describe("typing", () => {
     });
 
     it("mixed type tuple", () => {
-      const result = map.indexed(
+      const result = map(
         [1, "2", true] as [number, string, boolean],
         (_, index) => index,
       );
@@ -285,7 +262,7 @@ describe("typing", () => {
     });
 
     it("readonly mixed type tuple", () => {
-      const result = map.indexed(
+      const result = map(
         [1, "2", true] as readonly [number, string, boolean],
         (_, index) => index,
       );
@@ -293,7 +270,7 @@ describe("typing", () => {
     });
 
     it("nonempty (tail) number array", () => {
-      const result = map.indexed(
+      const result = map(
         [1, 2, 3] as [number, ...Array<number>],
         (x, index) => x + index,
       );
@@ -301,7 +278,7 @@ describe("typing", () => {
     });
 
     it("nonempty (tail) readonly number array", () => {
-      const result = map.indexed(
+      const result = map(
         [1, 2, 3] as readonly [number, ...Array<number>],
         (x, index) => x + index,
       );
@@ -309,7 +286,7 @@ describe("typing", () => {
     });
 
     it("nonempty (head) number array", () => {
-      const result = map.indexed(
+      const result = map(
         [1, 2, 3] as [...Array<number>, number],
         (x, index) => x + index,
       );
@@ -317,28 +294,11 @@ describe("typing", () => {
     });
 
     it("nonempty readonly (head) number array", () => {
-      const result = map.indexed(
+      const result = map(
         [1, 2, 3] as readonly [...Array<number>, number],
         (x, index) => x + index,
       );
       expectTypeOf(result).toEqualTypeOf<[...Array<number>, number]>();
     });
-
-    it("complex variadic number array", () => {
-      const result = map.indexed(
-        ["hello", "world", 1, "testing", "testing", "testing", 123, true] as [
-          ...Array<"hello">,
-          "world",
-          ...Array<number>,
-          string,
-          ...Array<number>,
-          boolean,
-        ],
-        identity,
-      );
-      expectTypeOf(result).toEqualTypeOf<
-        [...Array<boolean | number | string>, boolean | number | string]
-      >();
-    });
   });
 });
diff --git a/src/mapToObj.test.ts b/src/mapToObj.test.ts
index 44d087f..bca16c8 100644
--- a/src/mapToObj.test.ts
+++ b/src/mapToObj.test.ts
@@ -3,11 +3,11 @@ import { pipe } from "./pipe";
 
 describe("data_first", () => {
   it("mapToObj", () => {
-    const result = mapToObj([1, 2, 3] as const, (x) => [String(x), x * 2]);
+    const result = mapToObj([1, 2, 3], (x) => [String(x), x * 2]);
     expect(result).toEqual({ 1: 2, 2: 4, 3: 6 });
   });
-  it("mapToObj.indexed", () => {
-    const result = mapToObj.indexed([0, 0, 0] as const, (_, i) => [i, i]);
+  it("indexed", () => {
+    const result = mapToObj([0, 0, 0], (_, i) => [i, i]);
     expect(result).toEqual({ 0: 0, 1: 1, 2: 2 });
   });
 });
@@ -15,15 +15,15 @@ describe("data_first", () => {
 describe("data_last", () => {
   it("mapToObj", () => {
     const result = pipe(
-      [1, 2, 3] as const,
+      [1, 2, 3],
       mapToObj((x) => [String(x), x * 2]),
     );
     expect(result).toEqual({ 1: 2, 2: 4, 3: 6 });
   });
-  it("mapToObj.indexed", () => {
+  it("indexed", () => {
     const result = pipe(
-      [0, 0, 0] as const,
-      mapToObj.indexed((_, i) => [i, i]),
+      [0, 0, 0],
+      mapToObj((_, i) => [i, i]),
     );
     expect(result).toEqual({ 0: 0, 1: 1, 2: 2 });
   });
diff --git a/src/mapWithFeedback.test.ts b/src/mapWithFeedback.test.ts
index 886c63f..ebcc9d5 100644
--- a/src/mapWithFeedback.test.ts
+++ b/src/mapWithFeedback.test.ts
@@ -1,23 +1,19 @@
-import { pipe } from "./pipe";
+import { map } from "./map";
 import { mapWithFeedback } from "./mapWithFeedback";
-import { createLazyInvocationCounter } from "../test/lazy_invocation_counter";
+import { pipe } from "./pipe";
 import { take } from "./take";
-import { add } from "./add";
 
-describe("data first", () => {
-  describe("base", () => {
+describe("runtime", () => {
+  describe("data first", () => {
     it("should return an array of successively accumulated values", () => {
-      const result = mapWithFeedback(
-        [1, 2, 3, 4, 5] as const,
-        (acc, x) => acc + x,
-        100,
-      );
-      expect(result).toEqual([101, 103, 106, 110, 115]);
+      expect(
+        mapWithFeedback([1, 2, 3, 4, 5], (acc, x) => acc + x, 100),
+      ).toStrictEqual([101, 103, 106, 110, 115]);
     });
 
     it("should use the same accumulator on every iteration if it's mutable, therefore returning an array containing {array length} references to the accumulator.", () => {
       const results = mapWithFeedback(
-        [1, 2, 3, 4, 5] as const,
+        [1, 2, 3, 4, 5],
         (acc, x) => {
           acc[x] = x;
           return acc;
@@ -26,7 +22,7 @@ describe("data first", () => {
       );
 
       const [item] = results;
-      expect(item).toEqual({ "1": 1, "2": 2, "3": 3, "4": 4, "5": 5 });
+      expect(item).toStrictEqual({ "1": 1, "2": 2, "3": 3, "4": 4, "5": 5 });
       for (const result of results) {
         expect(result).toBe(item);
       }
@@ -35,160 +31,66 @@ describe("data first", () => {
     it("if an empty array is provided, it should never iterate, returning a new empty array.", () => {
       const data: Array<unknown> = [];
       const result = mapWithFeedback(data, (acc) => acc, "value");
-      expect(result).toEqual([]);
+      expect(result).toStrictEqual([]);
       expect(result).not.toBe(data);
     });
 
-    it("should be compatible with transformer functions", () => {
-      expect(() => {
-        const result = mapWithFeedback([1, 2, 3, 4, 5], add, 100);
-        expect(result).toEqual([101, 103, 106, 110, 115]);
-      }).not.toThrowError("Wrong number of arguments");
-    });
-
-    it("indexed should not be compatible with transformer functions", () => {
-      expect(() => {
-        const result = mapWithFeedback.indexed([1, 2, 3, 4, 5], add, 100);
-        expect(result).toEqual([101, 103, 106, 110, 115]);
-      }).toThrowError("Wrong number of arguments");
-    });
-
-    describe("type test", () => {
-      it("should return a mutable tuple type whose length matches input container's length, consisting of the type of the initial value", () => {
-        const result = mapWithFeedback(
-          [1, 2, 3, 4, 5] as const,
-          (acc, x) => acc + x,
-          100,
-        );
-        expectTypeOf(result).toEqualTypeOf<
-          [number, number, number, number, number]
-        >();
-      });
-
-      it("should return a tuple consisting of the initial value type even if the initial iterable contains a different type", () => {
-        const result = mapWithFeedback(
-          ["1", "2", "3", "4", "5"] as const,
-          (acc, x) => acc + parseInt(x),
-          100,
-        );
-        expectTypeOf(result).toEqualTypeOf<
-          [number, number, number, number, number]
-        >();
-      });
-
-      it("should correctly infer type with a non-literal array type", () => {
-        const data: Array<number> = [1, 2, 3, 4, 5];
-        const result = mapWithFeedback(data, (acc, x) => acc + x, 100);
-        expectTypeOf(result).toEqualTypeOf<Array<number>>();
-      });
-    });
-  });
-
-  describe("indexed", () => {
     it("should track index and provide entire items array", () => {
-      const data = [1, 2, 3, 4, 5] as const;
+      const data = [1, 2, 3, 4, 5];
+
       const mockedReducer = vi.fn(
         (
           acc: number,
           x: number,
           _index: number,
           _items: ReadonlyArray<number>,
-        ): number => acc + x,
+        ) => acc + x,
       );
-      const expected: Array<Parameters<typeof mockedReducer>> = [
-        [100, 1, 0, [1, 2, 3, 4, 5]],
-        [101, 2, 1, [1, 2, 3, 4, 5]],
-        [103, 3, 2, [1, 2, 3, 4, 5]],
-        [106, 4, 3, [1, 2, 3, 4, 5]],
-        [110, 5, 4, [1, 2, 3, 4, 5]],
-      ];
-
-      mapWithFeedback.indexed(data, mockedReducer, 100);
 
-      expect(mockedReducer.mock.calls).toEqual(expected);
-    });
-    it("should return array of successively accumulated values", () => {
-      const result = mapWithFeedback.indexed(
-        [1, 2, 3, 4, 5] as const,
-        (acc, x) => acc + x,
-        100,
-      );
-      expect(result).toEqual([101, 103, 106, 110, 115]);
-    });
+      mapWithFeedback(data, mockedReducer, 100);
 
-    describe("type test", () => {
-      it("should return a mutable tuple type whose length matches input container's length, consisting of the type of the initial value", () => {
-        const result = mapWithFeedback.indexed(
-          [1, 2, 3, 4, 5] as const,
-          (acc, x) => acc + x,
-          100,
-        );
-        expectTypeOf(result).toEqualTypeOf<
-          [number, number, number, number, number]
-        >();
-      });
-
-      it("the items array passed to the callback should be an array type containing the union type of all of the members in the original array", () => {
-        mapWithFeedback.indexed(
-          [1, 2, 3, 4, 5] as const,
-          (acc, x, _index, items) => {
-            expectTypeOf(items).toEqualTypeOf<readonly [1, 2, 3, 4, 5]>();
-            return acc + x;
-          },
-          100,
-        );
-      });
+      expect(mockedReducer).toHaveBeenCalledWith(100, 1, 0, data);
+      expect(mockedReducer).toHaveBeenCalledWith(101, 2, 1, data);
+      expect(mockedReducer).toHaveBeenCalledWith(103, 3, 2, data);
+      expect(mockedReducer).toHaveBeenCalledWith(106, 4, 3, data);
+      expect(mockedReducer).toHaveBeenCalledWith(110, 5, 4, data);
     });
   });
-});
 
-describe("data last", () => {
-  describe("base", () => {
+  describe("data last", () => {
     it("should return an array of successively accumulated values", () => {
       expect(
         pipe(
-          [1, 2, 3, 4, 5] as const,
+          [1, 2, 3, 4, 5],
           mapWithFeedback((acc, x) => acc + x, 100),
         ),
-      ).toEqual([101, 103, 106, 110, 115]);
+      ).toStrictEqual([101, 103, 106, 110, 115]);
     });
 
     it("evaluates lazily", () => {
-      const counter = createLazyInvocationCounter();
+      const counter = vi.fn((x: number) => x);
       pipe(
-        [1, 2, 3, 4, 5] as const,
-        counter.fn(),
+        [1, 2, 3, 4, 5],
+        map(counter),
         mapWithFeedback((acc, x) => acc + x, 100),
         take(2),
       );
-      expect(counter.count).toHaveBeenCalledTimes(2);
+      expect(counter).toHaveBeenCalledTimes(2);
     });
 
-    it("should return a tuple consisting of the initial value type even if the initial iterable contains a different type", () => {
-      const result = pipe(
-        [1, 2, 3, 4, 5] as const,
-        mapWithFeedback((acc, x) => acc + x, 100),
-      );
-      expectTypeOf(result).toEqualTypeOf<
-        [number, number, number, number, number]
-      >();
-    });
-  });
-
-  describe("indexed", () => {
     it("should track index and progressively include elements from the original array in the items array during each iteration, forming a growing window", () => {
       const lazyItems: Array<Array<number>> = [];
       const indices: Array<number> = [];
       pipe(
-        [1, 2, 3, 4, 5] as const,
-        mapWithFeedback.indexed((acc, x, index, items) => {
+        [1, 2, 3, 4, 5],
+        mapWithFeedback((acc, x, index, items) => {
           indices.push(index);
           lazyItems.push([...items]);
           return acc + x;
         }, 100),
       );
-      expect(indices).toEqual([0, 1, 2, 3, 4]);
-      expect(lazyItems).toEqual([
+      expect(indices).toStrictEqual([0, 1, 2, 3, 4]);
+      expect(lazyItems).toStrictEqual([
         [1],
         [1, 2],
         [1, 2, 3],
@@ -196,26 +98,55 @@ describe("data last", () => {
         [1, 2, 3, 4, 5],
       ]);
     });
+  });
+});
 
-    it("evaluates lazily", () => {
-      const counter = createLazyInvocationCounter();
-      pipe(
-        [1, 2, 3, 4, 5] as const,
-        counter.fn(),
-        mapWithFeedback.indexed((acc, x) => acc + x, 100),
-        take(2),
-      );
-      expect(counter.count).toHaveBeenCalledTimes(2);
-    });
+describe("typing", () => {
+  it("should return a mutable tuple type whose length matches input container's length, consisting of the type of the initial value", () => {
+    const result = mapWithFeedback([1, 2, 3, 4, 5], (acc, x) => acc + x, 100);
+    expectTypeOf(result).toEqualTypeOf<
+      [number, number, number, number, number]
+    >();
+  });
 
-    it("should return a tuple consisting of the initial value type even if the initial iterable contains a different type", () => {
-      const result = pipe(
-        [1, 2, 3, 4, 5] as const,
-        mapWithFeedback.indexed((acc, x) => acc + x, 100),
-      );
-      expectTypeOf(result).toEqualTypeOf<
-        [number, number, number, number, number]
-      >();
-    });
+  it("should return a tuple consisting of the initial value type even if the initial iterable contains a different type", () => {
+    const result = mapWithFeedback(
+      ["1", "2", "3", "4", "5"],
+      (acc, x) => acc + parseInt(x),
+      100,
+    );
+    expectTypeOf(result).toEqualTypeOf<
+      [number, number, number, number, number]
+    >();
+  });
+
+  it("should correctly infer type with a non-literal array type", () => {
+    const result = mapWithFeedback(
+      [1, 2, 3, 4, 5] as Array<number>,
+      (acc, x) => acc + x,
+      100,
+    );
+    expectTypeOf(result).toEqualTypeOf<Array<number>>();
+  });
+
+  it("should return a tuple consisting of the initial value type even if the initial iterable contains a different type", () => {
+    const result = pipe(
+      [1, 2, 3, 4, 5] as const,
+      mapWithFeedback((acc, x) => acc + x, 100),
+    );
+    expectTypeOf(result).toEqualTypeOf<
+      [number, number, number, number, number]
+    >();
+  });
+
+  it("the items array passed to the callback should be an array type containing the union type of all of the members in the original array", () => {
+    mapWithFeedback(
+      [1, 2, 3, 4, 5] as const,
+      (acc, x, _index, items) => {
+        expectTypeOf(items).toEqualTypeOf<readonly [1, 2, 3, 4, 5]>();
+        return acc + x;
+      },
+      100,
+    );
   });
 });
diff --git a/src/meanBy.test.ts b/src/meanBy.test.ts
index f4537fb..c9979a1 100644
--- a/src/meanBy.test.ts
+++ b/src/meanBy.test.ts
@@ -1,19 +1,26 @@
+import { identity } from "./identity";
 import { meanBy } from "./meanBy";
 import { pipe } from "./pipe";
-
-const array = [{ a: 1 }, { a: 2 }, { a: 4 }, { a: 5 }, { a: 3 }] as const;
+import { prop } from "./prop";
 
 describe("data first", () => {
   test("meanBy", () => {
-    expect(meanBy(array, (x) => x.a)).toEqual(3);
+    expect(
+      meanBy([{ a: 1 }, { a: 2 }, { a: 4 }, { a: 5 }, { a: 3 }], prop("a")),
+    ).toEqual(3);
   });
 
-  test("meanBy.indexed", () => {
-    expect(meanBy.indexed(array, (x, idx) => x.a + idx)).toEqual(5);
+  test("indexed", () => {
+    expect(
+      meanBy(
+        [{ a: 1 }, { a: 2 }, { a: 4 }, { a: 5 }, { a: 3 }],
+        ({ a }, idx) => a + idx,
+      ),
+    ).toEqual(5);
   });
 
   test("should handle empty array", () => {
-    expect(meanBy([], (x) => x)).toBeNaN();
+    expect(meanBy([], identity)).toBeNaN();
   });
 });
 
@@ -21,27 +28,22 @@ describe("data last", () => {
   test("meanBy", () => {
     expect(
       pipe(
-        array,
-        meanBy((x) => x.a),
+        [{ a: 1 }, { a: 2 }, { a: 4 }, { a: 5 }, { a: 3 }],
+        meanBy(prop("a")),
       ),
     ).toEqual(3);
   });
 
-  test("meanBy.indexed", () => {
+  test("indexed", () => {
     expect(
       pipe(
-        array,
-        meanBy.indexed((x, idx) => x.a + idx),
+        [{ a: 1 }, { a: 2 }, { a: 4 }, { a: 5 }, { a: 3 }],
+        meanBy(({ a }, idx) => a + idx),
       ),
     ).toEqual(5);
   });
 
   test("should handle empty array", () => {
-    expect(
-      pipe(
-        [],
-        meanBy((x) => x),
-      ),
-    ).toBeNaN();
+    expect(pipe([], meanBy(identity))).toBeNaN();
   });
 });
diff --git a/src/partition.test.ts b/src/partition.test.ts
index cb7ec90..86a164e 100644
--- a/src/partition.test.ts
+++ b/src/partition.test.ts
@@ -2,66 +2,126 @@ import { isNumber } from "./isNumber";
 import { partition } from "./partition";
 import { pipe } from "./pipe";
 
-const array = [
-  { a: 1, b: 1 },
-  { a: 1, b: 2 },
-  { a: 2, b: 1 },
-  { a: 1, b: 3 },
-] as const;
-const expected = [
-  [
-    { a: 1, b: 1 },
-    { a: 1, b: 2 },
-    { a: 1, b: 3 },
-  ],
-  [{ a: 2, b: 1 }],
-];
+describe("runtime", () => {
+  describe("data first", () => {
+    test("partition", () => {
+      expect(
+        partition(
+          [
+            { a: 1, b: 1 },
+            { a: 1, b: 2 },
+            { a: 2, b: 1 },
+            { a: 1, b: 3 },
+          ],
+          ({ a }) => a === 1,
+        ),
+      ).toEqual([
+        [
+          { a: 1, b: 1 },
+          { a: 1, b: 2 },
+          { a: 1, b: 3 },
+        ],
+        [{ a: 2, b: 1 }],
+      ]);
+    });
 
-describe("data first", () => {
-  test("partition", () => {
-    expect(partition(array, (x) => x.a === 1)).toEqual(expected);
+    test("partition with type guard", () => {
+      expect(partition([1, "a", 2, "b"], isNumber)).toEqual([
+        [1, 2],
+        ["a", "b"],
+      ]);
+    });
+
+    test("partition with type guard in pipe", () => {
+      expect(
+        pipe(
+          [1, "a", 2, "b"],
+          partition((value): value is number => typeof value === "number"),
+        ),
+      ).toEqual([
+        [1, 2],
+        ["a", "b"],
+      ]);
+    });
+
+    test("indexed", () => {
+      expect(
+        partition(
+          [
+            { a: 1, b: 1 },
+            { a: 1, b: 2 },
+            { a: 2, b: 1 },
+            { a: 1, b: 3 },
+          ],
+          (_, index) => index !== 2,
+        ),
+      ).toEqual([
+        [
+          { a: 1, b: 1 },
+          { a: 1, b: 2 },
+          { a: 1, b: 3 },
+        ],
+        [{ a: 2, b: 1 }],
+      ]);
+    });
   });
+
+  describe("data last", () => {
+    test("partition", () => {
+      expect(
+        pipe(
+          [
+            { a: 1, b: 1 },
+            { a: 1, b: 2 },
+            { a: 2, b: 1 },
+            { a: 1, b: 3 },
+          ],
+          partition(({ a }) => a === 1),
+        ),
+      ).toEqual([
+        [
+          { a: 1, b: 1 },
+          { a: 1, b: 2 },
+          { a: 1, b: 3 },
+        ],
+        [{ a: 2, b: 1 }],
+      ]);
+    });
+
+    test("indexed", () => {
+      expect(
+        pipe(
+          [
+            { a: 1, b: 1 },
+            { a: 1, b: 2 },
+            { a: 2, b: 1 },
+            { a: 1, b: 3 },
+          ],
+          partition((_, index) => index !== 2),
+        ),
+      ).toEqual([
+        [
+          { a: 1, b: 1 },
+          { a: 1, b: 2 },
+          { a: 1, b: 3 },
+        ],
+        [{ a: 2, b: 1 }],
+      ]);
+    });
+  });
+});
+
+describe("typing", () => {
   test("partition with type guard", () => {
     const actual = partition([1, "a", 2, "b"], isNumber);
-    expect(actual).toEqual([
-      [1, 2],
-      ["a", "b"],
-    ]);
-    assertType<[Array<number>, Array<string>]>(actual);
+    expectTypeOf(actual).toEqualTypeOf<[Array<number>, Array<string>]>();
   });
+
   test("partition with type guard in pipe", () => {
     const actual = pipe(
       [1, "a", 2, "b"],
       partition((value): value is number => typeof value === "number"),
     );
-    expect(actual).toEqual([
-      [1, 2],
-      ["a", "b"],
-    ]);
-    assertType<[Array<number>, Array<string>]>(actual);
-  });
-  test("partition.indexed", () => {
-    expect(partition.indexed(array, (_, index) => index !== 2)).toEqual(
-      expected,
-    );
-  });
-});
-
-describe("data last", () => {
-  test("partition", () => {
-    expect(
-      pipe(
-        array,
-        partition((x) => x.a === 1),
-      ),
-    ).toEqual(expected);
-  });
-  test("partition.indexed", () => {
-    expect(
-      pipe(
-        array,
-        partition.indexed((_, index) => index !== 2),
-      ),
-    ).toEqual(expected);
+    expectTypeOf(actual).toEqualTypeOf<[Array<number>, Array<string>]>();
   });
 });
diff --git a/src/reduce.test.ts b/src/reduce.test.ts
index 39b3c2a..38c22a3 100644
--- a/src/reduce.test.ts
+++ b/src/reduce.test.ts
@@ -1,20 +1,21 @@
 import { pipe } from "./pipe";
 import { reduce } from "./reduce";
 
-const array = [1, 2, 3, 4, 5] as const;
-
 describe("data first", () => {
   test("reduce", () => {
-    expect(reduce(array, (acc, x) => acc + x, 100)).toEqual(115);
+    expect(reduce([1, 2, 3, 4, 5], (acc, x) => acc + x, 100)).toEqual(115);
   });
-  test("reduce.indexed", () => {
+
+  test("indexed", () => {
+    const data = [1, 2, 3, 4, 5];
     let i = 0;
+
     expect(
-      reduce.indexed(
-        array,
+      reduce(
+        data,
         (acc, x, index, items) => {
           expect(index).toBe(i);
-          expect(items).toBe(array);
+          expect(items).toBe(data);
           i += 1;
           return acc + x;
         },
@@ -28,18 +29,9 @@ describe("data last", () => {
   test("reduce", () => {
     expect(
       pipe(
-        array,
+        [1, 2, 3, 4, 5],
         reduce((acc, x) => acc + x, 100),
       ),
     ).toEqual(115);
   });
-
-  test("reduce.indexed", () => {
-    expect(
-      pipe(
-        array,
-        reduce.indexed((acc, x) => acc + x, 100),
-      ),
-    ).toEqual(115);
-  });
 });
diff --git a/src/sortedIndexBy.test.ts b/src/sortedIndexBy.test.ts
index c3c6295..b62414d 100644
--- a/src/sortedIndexBy.test.ts
+++ b/src/sortedIndexBy.test.ts
@@ -261,7 +261,7 @@ function indicesSeen<T>(
   valueFunction: (item: T) => NonNullable<unknown>,
 ): ReadonlyArray<number | undefined> {
   const indices: Array<number | undefined> = [];
-  sortedIndexBy.indexed(items, item, (pivot, index) => {
+  sortedIndexBy(items, item, (pivot, index) => {
     indices.push(index);
     return valueFunction(pivot);
   });
diff --git a/src/sortedIndexWith.test.ts b/src/sortedIndexWith.test.ts
index e9dd19e..0e785c5 100644
--- a/src/sortedIndexWith.test.ts
+++ b/src/sortedIndexWith.test.ts
@@ -14,7 +14,7 @@ describe("sanity", () => {
 
   test("indexed", () => {
     expect(
-      sortedIndexWith.indexed(
+      sortedIndexWith(
         ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"],
         (_, index) => index < 5,
       ),
diff --git a/src/sortedLastIndexBy.test.ts b/src/sortedLastIndexBy.test.ts
index a2a77c7..d71370f 100644
--- a/src/sortedLastIndexBy.test.ts
+++ b/src/sortedLastIndexBy.test.ts
@@ -261,7 +261,7 @@ function indicesSeen<T>(
   valueFunction: (item: T) => NonNullable<unknown>,
 ): ReadonlyArray<number | undefined> {
   const indices: Array<number | undefined> = [];
-  sortedLastIndexBy.indexed(items, item, (pivot, index) => {
+  sortedLastIndexBy(items, item, (pivot, index) => {
     indices.push(index);
     return valueFunction(pivot);
   });
diff --git a/src/sumBy.test.ts b/src/sumBy.test.ts
index 788787f..ff61225 100644
--- a/src/sumBy.test.ts
+++ b/src/sumBy.test.ts
@@ -1,16 +1,21 @@
 import { pipe } from "./pipe";
+import { prop } from "./prop";
 import { sumBy } from "./sumBy";
 
-const array = [{ a: 1 }, { a: 2 }, { a: 4 }, { a: 5 }, { a: 3 }] as const;
-const expected = 15;
-
 describe("data first", () => {
   test("sumBy", () => {
-    expect(sumBy(array, (x) => x.a)).toEqual(expected);
+    expect(
+      sumBy([{ a: 1 }, { a: 2 }, { a: 4 }, { a: 5 }, { a: 3 }], prop("a")),
+    ).toEqual(15);
   });
 
-  test("sumBy.indexed", () => {
-    expect(sumBy.indexed(array, (x, idx) => x.a + idx)).toEqual(25);
+  test("indexed", () => {
+    expect(
+      sumBy(
+        [{ a: 1 }, { a: 2 }, { a: 4 }, { a: 5 }, { a: 3 }],
+        ({ a }, idx) => a + idx,
+      ),
+    ).toEqual(25);
   });
 });
 
@@ -18,16 +23,16 @@ describe("data last", () => {
   test("sumBy", () => {
     expect(
       pipe(
-        array,
-        sumBy((x) => x.a),
+        [{ a: 1 }, { a: 2 }, { a: 4 }, { a: 5 }, { a: 3 }],
+        sumBy(prop("a")),
       ),
-    ).toEqual(expected);
+    ).toEqual(15);
   });
 
-  test("sumBy.indexed", () => {
+  test("indexed", () => {
     const actual = pipe(
-      array,
-      sumBy.indexed((x, idx) => x.a + idx),
+      [{ a: 1 }, { a: 2 }, { a: 4 }, { a: 5 }, { a: 3 }],
+      sumBy(({ a }, idx) => a + idx),
     );
     expect(actual).toEqual(25);
   });