Repository: marpple/FxTS (ts)

Issue:
Add `difference` function
# Suggestion

<!--
  Please fill in each section completely. Thank you!
-->

## ⭐ Suggestion

<!-- What would you like to be added? -->

Returns Iterable/AsyncIterable of all elements in the `iterable2` not contained in the `iterable1`.

## 💻 Use Cases

<!-- What do you want to use this for? -->


```ts
const iter = difference([2,1], [2,3]);
iter.next(); // {value: 3, done:false}
iter.next(); // {value: undefined, done: true}
```

Pull request:
feat: add `difference` function
Fixes #85 


Patch:
diff --git a/src/Lazy/difference.ts b/src/Lazy/difference.ts
new file mode 100644
index 00000000..611add88
--- /dev/null
+++ b/src/Lazy/difference.ts
@@ -0,0 +1,83 @@
+import identity from "../identity";
+import IterableInfer from "../types/IterableInfer";
+import { isAsyncIterable, isIterable } from "../_internal/utils";
+import differenceBy from "./differenceBy";
+
+/**
+ * Returns Iterable/AsyncIterable of all elements in the iterable2 not contained in the iterable1.
+ *
+ * @example
+ * ```ts
+ * const iter = difference([2, 1], [2, 3, 4]);
+ * iter.next(); // {value: 3, done:false}
+ * iter.next(); // {value: 4, done:false}
+ * iter.next(); // {value: undefined, done: true}
+ * ```
+ */
+function difference<T>(
+  iterable1: Iterable<T>,
+  iterable2: Iterable<T>,
+): IterableIterator<T>;
+
+function difference<T>(
+  iterable1: AsyncIterable<T>,
+  iterable2: Iterable<T>,
+): AsyncIterableIterator<T>;
+
+function difference<T>(
+  iterable1: Iterable<T>,
+  iterable2: AsyncIterable<T>,
+): AsyncIterableIterator<T>;
+
+function difference<T>(
+  iterable1: AsyncIterable<T>,
+  iterable2: AsyncIterable<T>,
+): AsyncIterableIterator<T>;
+
+function difference<
+  A extends Iterable<unknown> | AsyncIterable<unknown>,
+  B extends Iterable<unknown> | AsyncIterable<unknown>,
+>(
+  iterable1: A,
+): (
+  iterable2: B,
+) => A extends AsyncIterable<unknown>
+  ? AsyncIterableIterator<IterableInfer<B>>
+  : B extends AsyncIterable<unknown>
+  ? AsyncIterableIterator<IterableInfer<B>>
+  : IterableIterator<IterableInfer<B>>;
+
+function difference<T>(
+  iterable1: Iterable<T> | AsyncIterable<T>,
+  iterable2?: Iterable<T> | AsyncIterable<T>,
+):
+  | IterableIterator<T>
+  | AsyncIterableIterator<T>
+  | ((
+      iterable2: Iterable<T> | AsyncIterable<T>,
+    ) => IterableIterator<T> | AsyncIterableIterator<T>) {
+  if (iterable2 === undefined) {
+    return (iterable2: any) => {
+      return difference(iterable1 as any, iterable2);
+    };
+  }
+
+  if (isIterable(iterable1) && isIterable(iterable2)) {
+    return differenceBy(identity, iterable1, iterable2);
+  }
+  if (isIterable(iterable1) && isAsyncIterable(iterable2)) {
+    return differenceBy(identity, iterable1, iterable2);
+  }
+  if (isAsyncIterable(iterable1) && isIterable(iterable2)) {
+    return differenceBy(identity, iterable1, iterable2);
+  }
+  if (isAsyncIterable(iterable1) && isAsyncIterable(iterable2)) {
+    return differenceBy(identity, iterable1, iterable2);
+  }
+
+  throw new TypeError(
+    "'iterable1' and 'iterable2' must be type of Iterable or AsyncIterable",
+  );
+}
+
+export default difference;
diff --git a/src/Lazy/differenceBy.ts b/src/Lazy/differenceBy.ts
index 0c4480ba..69b2c2bd 100644
--- a/src/Lazy/differenceBy.ts
+++ b/src/Lazy/differenceBy.ts
@@ -10,13 +10,13 @@ const hasValue = (set: Set<unknown>, a: unknown) => {
   return pipe1(a, (b) => set.has(b));
 };
 
-function* sync<T>(
+function sync<T>(
   f: (a: T) => unknown,
   iterable1: Iterable<T>,
   iterable2: Iterable<T>,
 ) {
   const set = new Set(map(f, iterable1));
-  yield* reject((a) => hasValue(set, f(a)), iterable2);
+  return reject((a) => hasValue(set, f(a)), iterable2);
 }
 
 async function* asyncSequential<T>(
diff --git a/src/Lazy/index.ts b/src/Lazy/index.ts
index 86852b4f..d3316b46 100644
--- a/src/Lazy/index.ts
+++ b/src/Lazy/index.ts
@@ -4,6 +4,7 @@ import compact from "./compact";
 import concat from "./concat";
 import concurrent from "./concurrent";
 import cycle from "./cycle";
+import difference from "./difference";
 import differenceBy from "./differenceBy";
 import drop from "./drop";
 import dropWhile from "./dropWhile";
@@ -35,6 +36,7 @@ export {
   concat,
   concurrent,
   cycle,
+  difference,
   differenceBy,
   drop,
   dropWhile,
diff --git a/website/docs_md/getting-started.md b/website/docs_md/getting-started.md
index 138f069a..395c46ea 100644
--- a/website/docs_md/getting-started.md
+++ b/website/docs_md/getting-started.md
@@ -48,7 +48,7 @@ const sum = pipe(
   map((a) => a * 10),
   take(10),
   reduce((a, b) => a + b),
-); // typeof 'sum' inferred as the number
+); // typeof 'sum' inffered as the number
 ```
 
 **Note: It is recommended to enable [strictFunctionTypes](https://www.typescriptlang.org/tsconfig#strictFunctionTypes) tsc option. If not, type inference does not work as we expected. For example, in the example above, `sum` is not inferred as a number type if the option is turned off.**


Test patch:
diff --git a/test/Lazy/difference.spec.ts b/test/Lazy/difference.spec.ts
new file mode 100644
index 00000000..6ea5ce4c
--- /dev/null
+++ b/test/Lazy/difference.spec.ts
@@ -0,0 +1,83 @@
+import { delay, map, pipe, toArray, toAsync } from "../../src";
+import concurrent, { Concurrent } from "../../src/Lazy/concurrent";
+import difference from "../../src/Lazy/difference";
+import { callFuncAfterTime, generatorMock } from "../utils";
+
+describe("difference", function () {
+  describe("sync", function () {
+    it.each([
+      [
+        [1, 2, 3, 4],
+        [3, 4, 5, 6],
+        [5, 6],
+      ],
+      ["abcd", "cdefg", ["e", "f", "g"]],
+    ])(
+      "should return all elements in %o not contained %o",
+      function (iterable1, iterable2, result) {
+        const iter = difference(iterable1 as any, iterable2 as any);
+        expect(toArray(iter)).toEqual(result);
+      },
+    );
+
+    it("should be able to be used as a curried function in the pipeline", function () {
+      const res = pipe([1, 2, 3, 4], difference([2, 4, 5, 6]), toArray);
+      expect(res).toEqual([1, 3]);
+    });
+  });
+
+  describe("async", function () {
+    it.each([
+      [toAsync([1, 2, 3, 4]), toAsync([3, 4, 5, 6]), [5, 6]],
+      [[1, 2, 3, 4], toAsync([3, 4, 5, 6]), [5, 6]],
+      [toAsync([1, 2, 3, 4]), [3, 4, 5, 6], [5, 6]],
+      ["abcd", "cdefg", ["e", "f", "g"]],
+    ])(
+      "should return all elements in iterable1 not contained iterable2",
+      async function (iterable1, iterable2, result) {
+        const iter = difference(
+          iterable1 as AsyncIterable<any>,
+          iterable2 as AsyncIterable<any>,
+        );
+        expect(await toArray(iter)).toEqual(result);
+      },
+    );
+
+    it("should be able to be used as a curried function in the pipeline", async function () {
+      const res = await pipe(
+        toAsync([1, 2, 3, 4]),
+        difference([2, 4, 5, 6]),
+        toArray,
+      );
+      expect(res).toEqual([1, 3]);
+    });
+
+    it("should be handled concurrently", async function () {
+      const fn = jest.fn();
+      callFuncAfterTime(fn, 900);
+
+      const res = await pipe(
+        [1, 2, 3, 4, 5, 6, 7, 8, 9],
+        toAsync,
+        map((a) => delay(300, a)),
+        difference([3, 4, 5]),
+        concurrent(3),
+        toArray,
+      );
+
+      expect(fn).toBeCalled();
+      expect(res).toEqual([1, 2, 6, 7, 8, 9]);
+    }, 950);
+
+    it("should be passed concurrent object when job works concurrently", async function () {
+      const mock1 = generatorMock();
+      const mock2 = generatorMock();
+      const iter = difference(mock1, mock2);
+      const concurrent = Concurrent.of(2) as any;
+
+      await iter.next(concurrent);
+      // mock2 only concurrent
+      expect((mock2 as any).getConcurrent()).toEqual(concurrent);
+    });
+  });
+});
diff --git a/type-check/Lazy/difference.test.ts b/type-check/Lazy/difference.test.ts
new file mode 100644
index 00000000..d9347f94
--- /dev/null
+++ b/type-check/Lazy/difference.test.ts
@@ -0,0 +1,20 @@
+import * as Test from "../../src/types/Test";
+import { difference, pipe, toArray, toAsync } from "../../src";
+
+const { checks, check } = Test;
+
+const res1 = difference([1, 2, 3, 4], [5, 6, 3, 4]);
+const res2 = difference(toAsync([1, 2, 3, 4]), [5, 6, 3, 4]);
+const res3 = difference([1, 2, 3, 4], toAsync([5, 6, 3, 4]));
+const res4 = difference(toAsync([1, 2, 3, 4]), toAsync([5, 6, 3, 4]));
+const res5 = pipe([1, 2, 3, 4], difference([5, 6, 7, 8]), toArray);
+const res6 = pipe([1, 2, 3, 4], difference("abcd"), toArray);
+
+checks([
+  check<typeof res1, IterableIterator<number>, Test.Pass>(),
+  check<typeof res2, AsyncIterableIterator<number>, Test.Pass>(),
+  check<typeof res3, AsyncIterableIterator<number>, Test.Pass>(),
+  check<typeof res4, AsyncIterableIterator<number>, Test.Pass>(),
+  check<typeof res5, number[], Test.Pass>(),
+  check<typeof res6, number[], Test.Pass>(),
+]);