Repository: marpple/FxTS (ts)

Issue:
Add `includes` function
# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## ⭐ Suggestion

<!-- What would you like to be added? -->

Checks if the specified value is equal.

## 💻 Use Cases

<!-- What do you want to use this for? -->

```ts
includes('c', 'abcd'); // true
includes(3, [1,2,3,4]); // true
```


Pull request:
feat: add includes function
Fixes #46


Patch:
diff --git a/src/Lazy/concurrent.ts b/src/Lazy/concurrent.ts
index ec9ee66a..7dce9d0e 100644
--- a/src/Lazy/concurrent.ts
+++ b/src/Lazy/concurrent.ts
@@ -86,11 +86,11 @@ function concurrent<A>(
   }
 
   if (length === Infinity) {
-    throw new TypeError("'length' cannot be infinite");
+    throw new RangeError("'length' cannot be infinite");
   }
 
   if (length <= 0) {
-    throw new TypeError("'length' must be over 0");
+    throw new RangeError("'length' must be over 0");
   }
 
   if (!isAsyncIterable(iterable)) {
diff --git a/src/includes.ts b/src/includes.ts
new file mode 100644
index 00000000..08631e48
--- /dev/null
+++ b/src/includes.ts
@@ -0,0 +1,54 @@
+import { some } from ".";
+import ReturnValueType from "./types/ReturnValueType";
+import { isAsyncIterable, isIterable } from "./_internal/utils";
+
+/**
+ * Checks if the specified value is equal. (contains)
+ *
+ * @example
+ * ```ts
+ * includes('c', 'abcd'); // true
+ * includes('e', 'abcd'); // false
+ * includes(3, [1,2,3,4]); // true
+ * includes(5, [1,2,3,4]); // false
+ * ```
+ */
+function includes<T extends readonly []>(value: unknown, iterable: T): false;
+
+// prettier-ignore
+function includes<T>(
+    value: unknown, 
+    iterable: Iterable<T>
+): boolean;
+
+function includes<T>(
+  value: unknown,
+  iterable: AsyncIterable<T>,
+): Promise<boolean>;
+
+function includes<T extends Iterable<unknown> | AsyncIterable<unknown>>(
+  value: unknown,
+): (iterable: T) => ReturnValueType<T, boolean>;
+
+function includes<T extends Iterable<unknown> | AsyncIterable<unknown>>(
+  value: unknown,
+  iterable?: T,
+): boolean | Promise<boolean> | ((iterable: T) => ReturnValueType<T, boolean>) {
+  if (iterable === undefined) {
+    return (iterable) => {
+      return includes(value, iterable as any) as ReturnValueType<T, boolean>;
+    };
+  }
+
+  if (isIterable(iterable)) {
+    return some((a) => a === value, iterable);
+  }
+
+  if (isAsyncIterable(iterable)) {
+    return some((a) => a === value, iterable);
+  }
+
+  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
+}
+
+export default includes;
diff --git a/src/index.ts b/src/index.ts
index fd3cf699..353adfcb 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -8,6 +8,7 @@ import findIndex from "./findIndex";
 import groupBy from "./groupBy";
 import head from "./head";
 import identity from "./identity";
+import includes from "./includes";
 import indexBy from "./indexBy";
 import isArray from "./isArray";
 import isNil from "./isNil";
@@ -15,6 +16,7 @@ import isString from "./isString";
 import join from "./join";
 import last from "./last";
 import not from "./not";
+import nth from "./nth";
 import omit from "./omit";
 import partition from "./partition";
 import pick from "./pick";
@@ -37,6 +39,8 @@ export {
   head,
   head as first,
   identity,
+  includes,
+  includes as contains,
   indexBy,
   isArray,
   isNil,
@@ -44,6 +48,7 @@ export {
   join,
   last,
   not,
+  nth,
   omit,
   partition,
   pick,
diff --git a/src/nth.ts b/src/nth.ts
new file mode 100644
index 00000000..7ecfdfe6
--- /dev/null
+++ b/src/nth.ts
@@ -0,0 +1,91 @@
+import IterableInfer from "./types/IterableInfer";
+import ReturnValueType from "./types/ReturnValueType";
+import { isAsyncIterable, isIterable } from "./_internal/utils";
+
+function sync<T>(index: number, iterable: Iterable<T>): T | undefined {
+  let idx = 0;
+  for (const item of iterable) {
+    if (idx++ === index) {
+      return item;
+    }
+  }
+}
+
+async function async<T>(
+  index: number,
+  iterable: AsyncIterable<T>,
+): Promise<T | undefined> {
+  let idx = 0;
+  for await (const item of iterable) {
+    if (idx++ === index) {
+      return item;
+    }
+  }
+}
+
+/**
+ * Returns the nth element of the given Iterable/AsyncIterable
+ *
+ * @example
+ * ```ts
+ * nth(2, [1,2,3,4]); // 1
+ * nth(5, [1,2,3,4]); // undefined
+ * nth(2, ['name', 'gender', 'age']); // 'age'
+ * nth(3, ['name', 'gender', 'age']); // undefined
+ * nth(2, 'abcdefg'); // 'b'
+ * nth(10, 'abcdefg'); // undefined
+ * ```
+ */
+// prettier-ignore
+function nth(
+  index: number, 
+  iterable: readonly []
+): undefined;
+
+// prettier-ignore
+function nth<T>(
+  index: number, 
+  iterable: Iterable<T>,
+): T | undefined;
+
+function nth<T>(
+  index: number,
+  iterable: AsyncIterable<T>,
+): Promise<T | undefined>;
+
+function nth<T extends Iterable<unknown> | AsyncIterable<unknown>>(
+  index: number,
+): (iterable: T) => ReturnValueType<T, IterableInfer<T> | undefined>;
+
+function nth<T extends Iterable<unknown> | AsyncIterable<unknown>>(
+  index: number,
+  iterable?: T,
+):
+  | undefined
+  | IterableInfer<T>
+  | Promise<IterableInfer<T> | undefined>
+  | ((iterable: T) => ReturnValueType<T, IterableInfer<T> | undefined>) {
+  if (iterable === undefined) {
+    return (iterable: T) => {
+      return nth(index, iterable as any) as ReturnValueType<
+        T,
+        IterableInfer<T> | undefined
+      >;
+    };
+  }
+
+  if (index < 0) {
+    throw new RangeError("'index' must be over 0");
+  }
+
+  if (isIterable(iterable)) {
+    return sync(index, iterable as Iterable<IterableInfer<T>>);
+  }
+  if (isAsyncIterable(iterable)) {
+    return async(index, iterable as AsyncIterable<IterableInfer<T>>);
+  }
+
+  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
+}
+
+export default nth;


Test patch:
diff --git a/test/includes.spec.ts b/test/includes.spec.ts
new file mode 100644
index 00000000..171cf942
--- /dev/null
+++ b/test/includes.spec.ts
@@ -0,0 +1,43 @@
+import { filter, includes, map, pipe, toAsync } from "../src/index";
+
+describe("includes", function () {
+  describe("sync", function () {
+    it("should check if the specified value is equal.", function () {
+      expect(includes("r", "marpple")).toEqual(true);
+      expect(includes("b", "marpple")).toEqual(false);
+
+      expect(includes(1, [1, 2, 3, 4])).toEqual(true);
+      expect(includes(5, [1, 2, 3, 4])).toEqual(false);
+    });
+
+    it("should be able to be used as a curried function in the pipeline", function () {
+      const res1 = pipe(
+        [1, 2, 3, 4],
+        map((a) => a + 10),
+        filter((a) => a % 2 === 0),
+        includes(14),
+      );
+      expect(res1).toEqual(true);
+    });
+  });
+
+  describe("async", function () {
+    it("should check if the specified value is equal.", async function () {
+      expect(await includes("r", toAsync("marpple"))).toEqual(true);
+      expect(await includes("b", toAsync("marpple"))).toEqual(false);
+
+      expect(await includes(1, toAsync([1, 2, 3, 4]))).toEqual(true);
+      expect(await includes(5, toAsync([1, 2, 3, 4]))).toEqual(false);
+    });
+  });
+
+  it("should be able to be used as a curried function in the pipeline", async function () {
+    const res1 = await pipe(
+      toAsync([1, 2, 3, 4]),
+      map((a) => a + 10),
+      filter((a) => a % 2 === 0),
+      includes(14),
+    );
+    expect(res1).toEqual(true);
+  });
+});
diff --git a/test/nth.spec.ts b/test/nth.spec.ts
new file mode 100644
index 00000000..d71311dc
--- /dev/null
+++ b/test/nth.spec.ts
@@ -0,0 +1,44 @@
+import { filter, nth, map, pipe, toAsync } from "../src/index";
+
+describe("nth", function () {
+  describe("sync", function () {
+    it("should return nth element", function () {
+      expect(nth(2, "marpple")).toEqual("r");
+      expect(nth(10, "marpple")).toEqual(undefined);
+      expect(() => nth(-1, "marpple")).toThrow();
+
+      expect(nth(1, [1, 2, 3, 4])).toEqual(2);
+      expect(nth(5, [1, 2, 3, 4])).toEqual(undefined);
+    });
+
+    it("should be able to be used as a curried function in the pipeline", function () {
+      const res = pipe(
+        [1, 2, 3, 4],
+        map((a) => a + 10),
+        filter((a) => a % 2 === 0),
+        nth(1),
+      );
+      expect(res).toEqual(14);
+    });
+  });
+
+  describe("async", function () {
+    it("should return nth element", async function () {
+      expect(await nth(2, toAsync("marpple"))).toEqual("r");
+      expect(await nth(10, toAsync("marpple"))).toEqual(undefined);
+
+      expect(await nth(1, toAsync([1, 2, 3, 4]))).toEqual(2);
+      expect(await nth(5, toAsync([1, 2, 3, 4]))).toEqual(undefined);
+    });
+  });
+
+  it("should be able to be used as a curried function in the pipeline", async function () {
+    const res1 = await pipe(
+      toAsync([1, 2, 3, 4]),
+      map((a) => a + 10),
+      filter((a) => a % 2 === 0),
+      nth(1),
+    );
+    expect(res1).toEqual(14);
+  });
+});
diff --git a/type-check/includes.test.ts b/type-check/includes.test.ts
new file mode 100644
index 00000000..7daa8510
--- /dev/null
+++ b/type-check/includes.test.ts
@@ -0,0 +1,21 @@
+import * as Test from "../src/types/Test";
+import { includes, pipe, toAsync } from "../src";
+
+const { checks, check } = Test;
+
+const res1 = includes(3, []); // false
+const res2 = includes(3, [1, 2, 3, 4, 5]);
+const res3 = includes("e", ["a", "b", "c", "d"]);
+const res4 = includes("e", toAsync(["a", "b", "c", "d"]));
+
+const res5 = pipe([1, 2, 3, 4, 5], includes(3));
+const res6 = pipe(["a", "b", "c", "d"], toAsync, includes(3));
+
+checks([
+  check<typeof res1, false, Test.Pass>(),
+  check<typeof res2, boolean, Test.Pass>(),
+  check<typeof res3, boolean, Test.Pass>(),
+  check<typeof res4, Promise<boolean>, Test.Pass>(),
+  check<typeof res5, boolean, Test.Pass>(),
+  check<typeof res6, Promise<boolean>, Test.Pass>(),
+]);
diff --git a/type-check/nth.test.ts b/type-check/nth.test.ts
new file mode 100644
index 00000000..6a513155
--- /dev/null
+++ b/type-check/nth.test.ts
@@ -0,0 +1,21 @@
+import * as Test from "../src/types/Test";
+import { nth, pipe, toAsync } from "../src";
+
+const { checks, check } = Test;
+
+const res1 = nth(3, []);
+const res2 = nth(3, [1, 2, 3, 4, 5]);
+const res3 = nth(3, ["a", "b", "c", "d"]);
+const res4 = nth(3, toAsync(["a", "b", "c", "d"]));
+
+const res5 = pipe([1, 2, 3, 4, 5], nth(3));
+const res6 = pipe(["a", "b", "c", "d"], toAsync, nth(3));
+
+checks([
+  check<typeof res1, undefined, Test.Pass>(),
+  check<typeof res2, number | undefined, Test.Pass>(),
+  check<typeof res3, string | undefined, Test.Pass>(),
+  check<typeof res4, Promise<string | undefined>, Test.Pass>(),
+  check<typeof res5, number | undefined, Test.Pass>(),
+  check<typeof res6, Promise<string | undefined>, Test.Pass>(),
+]);