Repository: marpple/FxTS (ts)

Issue:
Add `isEmpty` function
# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## ⭐ Suggestion

<!-- What would you like to be added? -->

Returns `true` if the given value is empty value, `false` otherwise.

## 💻 Use Cases

<!-- What do you want to use this for? -->

```ts
isEmpty(1); // false
isEmpty(0); // false
isEmpty(false); // false
isEmpty(true); // false
isEmpty(new Date()); // false
isEmpty(undefined); // true
isEmpty(null); // true

isEmpty({}); // true
isEmpty({a:1}); // false

isEmpty([]); // true
isEmpty([1]); // false

isEmpty(""); // true
isEmpty("a"); // false

isEmpty(function(){}); // false
isEmpty(Symbol("")); // false

```



Pull request:
feat: add `isEmpty` function
Fixes #71 


Patch:
diff --git a/src/apply.ts b/src/apply.ts
new file mode 100644
index 00000000..545d1bef
--- /dev/null
+++ b/src/apply.ts
@@ -0,0 +1,41 @@
+import Arrow from "./types/Arrow";
+
+/**
+ * Applies function `f` to the argument list `args`.
+ *
+ * @example
+ * ```ts
+ * apply(Math.max, [1, 2, 3, 4, 5]); // 5
+ *
+ * pipe(
+ *   repeat(10),
+ *   map(a => a * Math.random())
+ *   take(5),
+ *   apply(max)
+ * );
+ * ```
+ */
+
+function apply<F extends Arrow, T extends Parameters<F>>(
+  f: F,
+): (args: T) => ReturnType<typeof f>;
+
+function apply<F extends Arrow, T extends Parameters<F>>(
+  f: F,
+  args: T,
+): ReturnType<typeof f>;
+
+function apply<F extends Arrow, T extends Parameters<F>>(
+  f: F,
+  args?: T,
+):
+  | ReturnType<typeof f>
+  | ((args: Parameters<typeof f>) => ReturnType<typeof f>) {
+  if (args === undefined) {
+    return (args) => f(...args);
+  } else {
+    return f(...args);
+  }
+}
+
+export default apply;
diff --git a/src/consume.ts b/src/consume.ts
new file mode 100644
index 00000000..b043885a
--- /dev/null
+++ b/src/consume.ts
@@ -0,0 +1,68 @@
+import ReturnValueType from "./types/ReturnValueType";
+import { isAsyncIterable, isIterable } from "./_internal/utils";
+import range from "./Lazy/range";
+
+function sync<T>(iterable: Iterable<T>, n: number) {
+  const iterator = iterable[Symbol.iterator]();
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  for (const _ of range(0, n)) {
+    if (iterator.next().done) {
+      return;
+    }
+  }
+}
+
+async function async<T>(iterable: AsyncIterable<T>, n: number) {
+  const iterator = iterable[Symbol.asyncIterator]();
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  for await (const _ of range(0, n)) {
+    if ((await iterator.next()).done) {
+      return;
+    }
+  }
+}
+
+/**
+ * Consumes the given number of Iterable/AsyncIterable. If the number is empty, all is consumed.
+ *
+ * @example
+ * ```ts
+ * const iterator = (function *(){
+ *   yield 1;
+ *   yield 2;
+ *   yield 3;
+ * })();
+ * consume(iterator, 2);
+ * iterator.next(); // {value:3, done:false}
+ * iterator.next(); // {value:undefined, done:true}
+ *
+ * // with pipe
+ * pipe(
+ *   range(10),
+ *   peek(updateApi),
+ *   concurrent(5),
+ *   consume,
+ * );
+ * ```
+ */
+function consume<
+  A extends Iterable<unknown> | AsyncIterable<unknown>,
+  B extends number,
+>(iterator: A, n?: B): ReturnValueType<A, void>;
+
+function consume<A extends Iterable<unknown> | AsyncIterable<unknown>>(
+  iterable: A,
+  n = Infinity,
+) {
+  if (isIterable(iterable)) {
+    return sync(iterable, n);
+  }
+
+  if (isAsyncIterable(iterable)) {
+    return async(iterable, n);
+  }
+
+  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
+}
+
+export default consume;
diff --git a/src/index.ts b/src/index.ts
index 39a81097..2054f2be 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,5 @@
+import apply from "./apply";
+import consume from "./consume";
 import countBy from "./countBy";
 import curry from "./curry";
 import delay from "./delay";
@@ -11,11 +13,13 @@ import identity from "./identity";
 import includes from "./includes";
 import indexBy from "./indexBy";
 import isArray from "./isArray";
+import isEmpty from "./isEmpty";
 import isNil from "./isNil";
 import isObject from "./isObject";
 import isString from "./isString";
 import join from "./join";
 import last from "./last";
+import noop from "./noop";
 import not from "./not";
 import nth from "./nth";
 import omit from "./omit";
@@ -32,6 +36,8 @@ import tap from "./tap";
 import toArray from "./toArray";
 
 export {
+  apply,
+  consume,
   countBy,
   curry,
   delay,
@@ -47,11 +53,13 @@ export {
   includes as contains,
   indexBy,
   isArray,
+  isEmpty,
   isNil,
   isObject,
   isString,
   join,
   last,
+  noop,
   not,
   nth,
   omit,
diff --git a/src/isEmpty.ts b/src/isEmpty.ts
new file mode 100644
index 00000000..4ebe169b
--- /dev/null
+++ b/src/isEmpty.ts
@@ -0,0 +1,62 @@
+/**
+ * Returns true if the given value is empty value, false otherwise.
+ *
+ * @example
+ * ```ts
+ * isEmpty(1); // false
+ * isEmpty(0); // false
+ * isEmpty(false); // false
+ * isEmpty(true); // false
+ * isEmpty(new Date()); // false
+ * isEmpty(undefined); // true
+ * isEmpty(null); // true
+ *
+ * isEmpty({}); // true
+ * isEmpty({a:1}); // false
+ *
+ * isEmpty([]); // true
+ * isEmpty([1]); // false
+ *
+ * isEmpty(""); // true
+ * isEmpty("a"); // false
+ *
+ * isEmpty(function(){}); // false
+ * isEmpty(Symbol("")); // false
+ * ```
+ */
+function isEmpty(value: unknown): boolean {
+  if (typeof value === "number" || typeof value === "boolean") {
+    return false;
+  }
+
+  if (typeof value === "undefined" || value === null) {
+    return true;
+  }
+
+  if (value instanceof Date) {
+    return false;
+  }
+
+  if (typeof value === "function") {
+    return false;
+  }
+
+  if (value instanceof Object && !Object.keys(value).length) {
+    return true;
+  }
+
+  if (Array.isArray(value)) {
+    if (value.length === 0) {
+      return true;
+    }
+    return false;
+  }
+
+  if (value === "") {
+    return true;
+  }
+
+  return false;
+}
+
+export default isEmpty;
diff --git a/src/noop.ts b/src/noop.ts
new file mode 100644
index 00000000..1753010e
--- /dev/null
+++ b/src/noop.ts
@@ -0,0 +1,12 @@
+/**
+ * Returns `undefined`
+ *
+ * @example
+ * ```ts
+ * noop() // undefined
+ * ```
+ */
+// eslint-disable-next-line @typescript-eslint/no-empty-function
+function noop(): void {}
+
+export default noop;


Test patch:
diff --git a/test/apply.spec.ts b/test/apply.spec.ts
new file mode 100644
index 00000000..e6a05f5b
--- /dev/null
+++ b/test/apply.spec.ts
@@ -0,0 +1,13 @@
+import { apply, range, pipe, toArray } from "../src/index";
+
+describe("apply", function () {
+  it("should apply given list to the function", function () {
+    const res = apply(Math.max, [1, 2, 3, 4, 5]);
+    expect(res).toEqual(5);
+  });
+
+  it("should be able to be used as a curried function in the pipeline", function () {
+    const res = pipe(range(1, 6), toArray, apply(Math.max));
+    expect(res).toEqual(5);
+  });
+});
diff --git a/test/consume.spec.ts b/test/consume.spec.ts
new file mode 100644
index 00000000..fee64ba0
--- /dev/null
+++ b/test/consume.spec.ts
@@ -0,0 +1,89 @@
+import { consume, peek, pipe, toAsync } from "../src/index";
+
+describe("consume", function () {
+  describe("sync", function () {
+    const testParameters = [
+      {
+        input: [1, 2, 3, 4, 5][Symbol.iterator](),
+        n: 2,
+        expected: 3,
+      },
+      {
+        input: [1, 2, 3, 4, 5][Symbol.iterator](),
+        n: undefined,
+        expected: undefined,
+      },
+      {
+        input: [1, 2, 3, 4, 5][Symbol.iterator](),
+        n: Infinity,
+        expected: undefined,
+      },
+    ];
+
+    it.each(testParameters)(
+      "should be consumed the given number",
+      ({ input, n, expected }) => {
+        consume(input, n);
+        expect(input.next().value).toEqual(expected);
+      },
+    );
+
+    it("should be able to be used as a curried function in the pipeline", function () {
+      const iter = [1, 2, 3, 4][Symbol.iterator]();
+      let res = 0;
+      pipe(
+        iter,
+        peek((a) => {
+          res += a;
+        }),
+        consume,
+      );
+
+      expect(res).toEqual(10);
+      expect(iter.next().done).toEqual(true);
+    });
+  });
+
+  describe("async", function () {
+    const testParameters = [
+      {
+        input: toAsync([1, 2, 3, 4, 5])[Symbol.asyncIterator](),
+        n: 2,
+        expected: 3,
+      },
+      {
+        input: toAsync([1, 2, 3, 4, 5])[Symbol.asyncIterator](),
+        n: undefined,
+        expected: undefined,
+      },
+      {
+        input: toAsync([1, 2, 3, 4, 5])[Symbol.asyncIterator](),
+        n: Infinity,
+        expected: undefined,
+      },
+    ];
+
+    it.each(testParameters)(
+      "should be consumed the given number",
+      async ({ input, n, expected }) => {
+        await consume(input, n);
+        expect((await input.next()).value).toEqual(expected);
+      },
+    );
+
+    it("should be able to be used as a curried function in the pipeline", async function () {
+      const iter = toAsync([1, 2, 3, 4])[Symbol.asyncIterator]();
+      let res = 0;
+      await pipe(
+        iter,
+        peek((a) => {
+          res += a;
+        }),
+        consume,
+      );
+
+      expect(res).toEqual(10);
+      expect((await iter.next()).done).toEqual(true);
+    });
+  });
+});
diff --git a/test/isEmpty.spec.ts b/test/isEmpty.spec.ts
new file mode 100644
index 00000000..613e1120
--- /dev/null
+++ b/test/isEmpty.spec.ts
@@ -0,0 +1,29 @@
+import isEmpty from "../src/isEmpty";
+
+describe("isEmpty", function () {
+  const testParameters = [
+    [1, false],
+    [0, false],
+    [false, false],
+    [true, false],
+    [new Date(), false],
+    [undefined, true],
+    [null, true],
+    [{}, true],
+    [{ a: 1 }, false],
+    [[], true],
+    [[1], false],
+    ["", true],
+    ["a", false],
+    [Symbol(""), false],
+    // eslint-disable-next-line @typescript-eslint/no-empty-function
+    [function () {}, false],
+  ] as [any, any][];
+
+  it.each(testParameters)(
+    "should Return `true` if the given value is empty value(%s)",
+    function (input, expected) {
+      expect(isEmpty(input)).toEqual(expected);
+    },
+  );
+});
diff --git a/test/noop.spec.ts b/test/noop.spec.ts
new file mode 100644
index 00000000..f293be99
--- /dev/null
+++ b/test/noop.spec.ts
@@ -0,0 +1,7 @@
+import { noop } from "../src/index";
+
+describe("noop", function () {
+  it("should return `undefined`", function () {
+    expect(noop()).toEqual(undefined);
+  });
+});
diff --git a/type-check/apply.test.ts b/type-check/apply.test.ts
new file mode 100644
index 00000000..0e2a1f01
--- /dev/null
+++ b/type-check/apply.test.ts
@@ -0,0 +1,12 @@
+import * as Test from "../src/types/Test";
+import { apply, pipe, range, toArray } from "../src";
+
+const { checks, check } = Test;
+
+const res1 = apply(Math.max, [1, 2, 3, 4, 5]);
+const res2 = pipe(range(1, 6), toArray, apply(Math.max));
+
+checks([
+  check<typeof res1, ReturnType<typeof Math.max>, Test.Pass>(),
+  check<typeof res2, ReturnType<typeof Math.max>, Test.Pass>(),
+]);
diff --git a/type-check/consume.test.ts b/type-check/consume.test.ts
new file mode 100644
index 00000000..29164bbc
--- /dev/null
+++ b/type-check/consume.test.ts
@@ -0,0 +1,16 @@
+import * as Test from "../src/types/Test";
+import { consume, pipe, toAsync } from "../src";
+
+const { checks, check } = Test;
+
+const res1 = consume([1, 2, 3]);
+const res2 = pipe([1, 2, 3], consume);
+const res3 = pipe([1, 2, 3], (res) => consume(res, 2));
+const res4 = pipe([1, 2, 3], toAsync, consume);
+
+checks([
+  check<typeof res1, void, Test.Pass>(),
+  check<typeof res2, void, Test.Pass>(),
+  check<typeof res3, void, Test.Pass>(),
+  check<typeof res4, Promise<void>, Test.Pass>(),
+]);
diff --git a/type-check/isEmpty.test.ts b/type-check/isEmpty.test.ts
new file mode 100644
index 00000000..4e9cd060
--- /dev/null
+++ b/type-check/isEmpty.test.ts
@@ -0,0 +1,14 @@
+import * as Test from "../src/types/Test";
+import { isEmpty, pipe } from "../src";
+
+const { checks, check } = Test;
+
+const res1 = isEmpty([]);
+const res2 = isEmpty([1]);
+const res3 = pipe([1], isEmpty);
+
+checks([
+  check<typeof res1, boolean, Test.Pass>(),
+  check<typeof res2, boolean, Test.Pass>(),
+  check<typeof res3, boolean, Test.Pass>(),
+]);
diff --git a/type-check/noop.test.ts b/type-check/noop.test.ts
new file mode 100644
index 00000000..a9ac8b3b
--- /dev/null
+++ b/type-check/noop.test.ts
@@ -0,0 +1,8 @@
+import * as Test from "../src/types/Test";
+import { noop } from "../src";
+
+const { checks, check } = Test;
+
+const res = noop();
+
+checks([check<typeof res, void, Test.Pass>()]);