Repository: marpple/FxTS (ts)

Issue:
Change `reduce` behavior, When there are two arguments for `reduce`, and the first iterator value is done:true

### suggestion
If there are three arguments for `reduce`, there is no issue.
In the case of two arguments for `reduce` and the first iterator value is done:true, I think it should throw an error

### why?
When there are two arguments for reduce, and the first iterator value is done:true, it creates an ambiguous situation regarding the return value. 
If we choose to pass `undefined` in this case, the return type should also reflect the potential presence of `undefined` type ex) `T | undefined`

### other case
This behavior also applies to `Array.prototype.reduce`, where the same error will occur.
<img width="476" alt="image" src="https://github.com/marpple/FxTS/assets/10924072/6769f488-c41c-4e7a-9217-4c62b11c6d71">

Also, for Iterator Helpers `reduce`, in the mentioned scenario, a TypeError should be raised   in https://tc39.es/proposal-iterator-helpers/#sec-iteratorprototype.reduce.

Pull request description:
feat: change `reduce` behavior, when empty iterable
Fixes #235 


Patch:
diff --git a/package-lock.json b/package-lock.json
index cea78592..ea475bf6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "@fxts/core",
-  "version": "0.21.0",
+  "version": "0.22.0",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "@fxts/core",
-      "version": "0.21.0",
+      "version": "0.22.0",
       "license": "Apache-2.0",
       "dependencies": {
         "tslib": "^2.6.0"
diff --git a/package.json b/package.json
index 2851ab12..e7464e80 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@fxts/core",
-  "version": "0.21.0",
+  "version": "0.22.0",
   "description": "A functional library for TypeScript/JavaScript programmers.",
   "main": "dist/cjs/index.js",
   "module": "dist/esm/index.js",
diff --git a/src/every.ts b/src/every.ts
index 1a8df5a2..32d645b6 100644
--- a/src/every.ts
+++ b/src/every.ts
@@ -58,7 +58,12 @@ function every<
     return pipe(
       map(f, iterable),
       takeUntil(not),
-      reduce((a, b) => a && b),
+      (acc) =>
+        reduce(
+          (a: boolean, b: boolean) => a && b,
+          true,
+          acc as Iterable<boolean>,
+        ),
       (a) => a ?? true,
       Boolean,
     );
@@ -68,7 +73,12 @@ function every<
     return pipe(
       map(f, iterable),
       takeUntil(not),
-      reduce((a, b) => a && b),
+      (acc) =>
+        reduce(
+          (a: boolean, b: boolean) => a && b,
+          true,
+          acc as AsyncIterable<boolean>,
+        ),
       (a) => a ?? true,
       Boolean,
     );
diff --git a/src/isEmpty.ts b/src/isEmpty.ts
index b42e6117..43457b25 100644
--- a/src/isEmpty.ts
+++ b/src/isEmpty.ts
@@ -11,6 +11,8 @@ import isNil from "./isNil";
  * isEmpty(null) // true
  * isEmpty(undefined) // true
  * isEmpty("") // true
+ * isEmpty(new Map()) // true
+ * isEmpty(new Set()) // true
  *
  * isEmpty(0) // false
  * isEmpty(false) // false
@@ -33,6 +35,8 @@ const isEmpty = <T>(value: T): boolean => {
 
   if (value === "") return true;
 
+  if (value instanceof Map || value instanceof Set) return value.size === 0; // if value is a Map or Set and have no item.
+
   return false;
 };
 
diff --git a/src/join.ts b/src/join.ts
index f51282c4..5a11f248 100644
--- a/src/join.ts
+++ b/src/join.ts
@@ -10,7 +10,11 @@ type ReturnJoinType<T extends Iterable<unknown> | AsyncIterable<unknown>> =
     : never;
 
 function sync<A>(sep: string, iterable: Iterable<A>) {
-  const res = reduce((a: string, b) => `${a}${sep}${b}`, iterable);
+  const res = reduce(
+    (a: string, b) => (a == "" ? `${b}` : `${a}${sep}${b}`),
+    "",
+    iterable,
+  );
   if (res == null) {
     return "";
   }
@@ -19,7 +23,11 @@ function sync<A>(sep: string, iterable: Iterable<A>) {
 }
 
 function async<A>(sep: string, iterable: AsyncIterable<A>) {
-  return reduce((a: string, b) => `${a}${sep}${b}`, iterable).then((res) => {
+  return reduce(
+    (a: string, b) => (a == "" ? `${b}` : `${a}${sep}${b}`),
+    "",
+    iterable,
+  ).then((res) => {
     if (res == null) {
       return "";
     }
diff --git a/src/reduce.ts b/src/reduce.ts
index 24664c3e..52749f5d 100644
--- a/src/reduce.ts
+++ b/src/reduce.ts
@@ -72,8 +72,6 @@ async function async<A, B>(
  * {@link https://fxts.dev/docs/map | map}, {@link https://fxts.dev/docs/filter | filter}
  */
 
-function reduce<A extends readonly []>(f: Arrow, iterable: A): undefined;
-
 function reduce<A extends readonly [], B>(f: Arrow, seed: B, iterable: A): B;
 
 function reduce<A>(f: (a: A, b: A) => A, iterable: Iterable<A>): A;
@@ -113,11 +111,7 @@ function reduce<A extends Iterable<unknown> | AsyncIterable<unknown>, B>(
   f: (a: B, b: IterableInfer<A>) => B,
   seed?: B | Iterable<IterableInfer<A>> | AsyncIterable<IterableInfer<A>>,
   iterable?: Iterable<IterableInfer<A>> | AsyncIterable<IterableInfer<A>>,
-):
-  | B
-  | undefined
-  | Promise<B | undefined>
-  | ((iterable: A) => ReturnValueType<A, B>) {
+): B | Promise<B> | ((iterable: A) => ReturnValueType<A, B>) {
   if (iterable === undefined) {
     if (seed === undefined) {
       return (iterable: A) =>
@@ -128,7 +122,7 @@ function reduce<A extends Iterable<unknown> | AsyncIterable<unknown>, B>(
       const iterator = seed[Symbol.iterator]();
       const { done, value } = iterator.next();
       if (done) {
-        return undefined;
+        throw new TypeError("'reduce' of empty iterable with no initial value");
       }
       return sync(f, value as B, {
         [Symbol.iterator]() {
@@ -141,8 +135,11 @@ function reduce<A extends Iterable<unknown> | AsyncIterable<unknown>, B>(
       const iterator = seed[Symbol.asyncIterator]();
       return iterator.next().then(({ done, value }) => {
         if (done) {
-          return undefined;
+          throw new TypeError(
+            "'reduce' of empty iterable with no initial value",
+          );
         }
+
         return async(f, value as Promise<B>, {
           [Symbol.asyncIterator]() {
             return iterator;
diff --git a/src/some.ts b/src/some.ts
index 521258b2..c216db9c 100644
--- a/src/some.ts
+++ b/src/some.ts
@@ -78,7 +78,12 @@ function some<
     return pipe(
       map(f, iterable),
       takeUntil(identity),
-      reduce((a, b) => a || b),
+      (acc) =>
+        reduce(
+          (a: boolean, b: boolean) => a || b,
+          false,
+          acc as Iterable<boolean>,
+        ),
       Boolean,
     );
   }
@@ -87,7 +92,12 @@ function some<
     return pipe(
       map(f, iterable),
       takeUntil(identity),
-      reduce((a, b) => a || b),
+      (acc) =>
+        reduce(
+          (a: boolean, b: boolean) => a || b,
+          false,
+          acc as AsyncIterable<boolean>,
+        ),
       Boolean,
     );
   }


Test patch:
diff --git a/test/isEmpty.spec.ts b/test/isEmpty.spec.ts
index 4cb2ad09..6cdecfc1 100644
--- a/test/isEmpty.spec.ts
+++ b/test/isEmpty.spec.ts
@@ -15,6 +15,10 @@ describe("isEmpty", function () {
     [[1], false],
     ["", true],
     ["a", false],
+    [new Map(), true],
+    [new Map([["key", "value"]]), false],
+    [new Set(), true],
+    [new Set(["value"]), false],
     [Symbol(""), false],
     // eslint-disable-next-line @typescript-eslint/no-empty-function
     [function () {}, false],
diff --git a/test/reduce.spec.ts b/test/reduce.spec.ts
index 3f0d4b97..5c1335ea 100644
--- a/test/reduce.spec.ts
+++ b/test/reduce.spec.ts
@@ -9,8 +9,8 @@ describe("reduce", function () {
       expect(reduce((a, b) => a + b, "seed", [])).toEqual("seed");
     });
 
-    it("should return 'undefined' when the given `iterable` is an empty array and initial value is absent", function () {
-      expect(reduce((a, b) => a + b, [])).toBeUndefined();
+    it("should be occured error when the given `iterable` is an empty array and initial value is absent", function () {
+      expect(() => reduce((a) => a, [])).toThrow();
     });
 
     it("should work given it is initial value", function () {
diff --git a/type-check/reduce.test.ts b/type-check/reduce.test.ts
index 53b85bbb..76bf2d20 100644
--- a/type-check/reduce.test.ts
+++ b/type-check/reduce.test.ts
@@ -3,7 +3,7 @@ import * as Test from "../src/types/Test";
 
 const { checks, check } = Test;
 
-const res1 = reduce((a, b) => a + b, []);
+const res1 = reduce((a) => a, []);
 const res2 = reduce((a, b) => a + b, "seed", []);
 const res3 = reduce((a, b) => a + b, 0, [1, 2, 3]);
 const res4 = reduce((a, b) => a + b, [1, 2, 3]);
@@ -52,7 +52,7 @@ const res16 = pipe(
 );
 
 checks([
-  check<typeof res1, undefined, Test.Pass>(),
+  check<typeof res1, never, Test.Pass>(),
   check<typeof res2, "seed", Test.Pass>(),
   check<typeof res3, number, Test.Pass>(),
   check<typeof res4, number, Test.Pass>(),