Repository: marpple/FxTS (ts)

Issue:
feature `dropRight`
# Suggestion

Hello! I have used `fxjs` in my project.
And after apply typescript, i want to migrate from `fxjs` to `fxts`.
So, `fxts` API is not provide `dropRight` function yet.

## ⭐ Suggestion

please add `dropRight` function.

## 💻 Use Cases

```ts
pipe(
  [1, 2, 3, 4, 5],
  dropRight(1),
);
// [1, 2, 3, 4]
```


Pull request description:
feat: add `dropRight` function
Fixes #134 


Patch:
diff --git a/src/Lazy/dropRight.ts b/src/Lazy/dropRight.ts
new file mode 100644
index 00000000..5ba104e1
--- /dev/null
+++ b/src/Lazy/dropRight.ts
@@ -0,0 +1,152 @@
+import isArray from "../isArray";
+import isString from "../isString";
+import toArray from "../toArray";
+import ReturnIterableIteratorType from "../types/ReturnIterableIteratorType";
+import { isAsyncIterable, isIterable } from "../_internal/utils";
+import concurrent, { isConcurrent } from "./concurrent";
+
+function* stringDropRight(length: number, str: string) {
+  const arr = Array.from(str);
+  for (let i = 0; i < str.length - length; i++) {
+    yield arr[i];
+  }
+}
+
+function* arrayDropRight<T>(length: number, arr: Array<T>) {
+  for (let i = 0; i < arr.length - length; i++) {
+    yield arr[i];
+  }
+}
+
+function* sync<T>(length: number, iterable: Iterable<T>) {
+  const arr = toArray(iterable);
+  for (let i = 0; i < arr.length - length; i++) {
+    yield arr[i];
+  }
+}
+
+async function* asyncSequential<T>(length: number, iterable: AsyncIterable<T>) {
+  const arr = await toArray(iterable);
+  for (let i = 0; i < arr.length - length; i++) {
+    yield arr[i];
+  }
+}
+
+function async<A>(
+  length: number,
+  iterable: AsyncIterable<A>,
+): AsyncIterableIterator<A> {
+  let iterator: AsyncIterator<A>;
+  return {
+    [Symbol.asyncIterator]() {
+      return this;
+    },
+
+    next(_concurrent: any) {
+      if (iterator === undefined) {
+        iterator = isConcurrent(_concurrent)
+          ? asyncSequential(length, concurrent(_concurrent.length, iterable))
+          : asyncSequential(length, iterable);
+      }
+
+      return iterator.next(_concurrent);
+    },
+  };
+}
+
+/**
+ * Returns all but the last `length` elements of the given iterable.
+ *
+ * @example
+ * ```ts
+ * const iter = dropRight(2, [1, 2, 3, 4]);
+ * iter.next() // {done:false, value: 3}
+ * iter.next() // {done:false, value: 4}
+ * iter.next() // {done:true, value: undefined}
+ *
+ * // with pipe
+ * pipe(
+ *  [1, 2, 3, 4],
+ *  dropRight(2),
+ *  toArray,
+ * ); // [1, 2]
+ *
+ * pipe(
+ *  "abcde",
+ *  dropRight(2),
+ *  toArray,
+ * ); // ["a", "b", "c"]
+ *
+ * await pipe(
+ *  Promise.resolve([1, 2, 3, 4]),
+ *  dropRight(2),
+ *  toArray,
+ * ); // [1, 2]
+ *
+ * // with toAsync
+ * await pipe(
+ *  [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3), Promise.resolve(4)],
+ *  toAsync,
+ *  dropRight(2),
+ *  toArray,
+ * ); // [1, 2]
+ * ```
+ *
+ * see {@link https://fxts.dev/docs/pipe | pipe}, {@link https://fxts.dev/docs/toAsync | toAsync},
+ * {@link https://fxts.dev/docs/toArray | toArray}
+ */
+// prettier-ignore
+function dropRight<A>(
+  length: number,
+  iterable: Iterable<A>
+): IterableIterator<A>;
+
+function dropRight<A>(
+  length: number,
+  iterable: AsyncIterable<A>,
+): AsyncIterableIterator<A>;
+
+function dropRight<A extends Iterable<unknown> | AsyncIterable<unknown>>(
+  length: number,
+): (iterable: A) => ReturnIterableIteratorType<A>;
+
+function dropRight<A extends Iterable<unknown> | AsyncIterable<unknown>>(
+  length: number,
+  iterable?: A,
+):
+  | IterableIterator<A>
+  | AsyncIterableIterator<A>
+  | ((iterable: A) => ReturnIterableIteratorType<A>) {
+  if (iterable === undefined) {
+    return (iterable: A): ReturnIterableIteratorType<A> => {
+      return dropRight(
+        length,
+        iterable as any,
+      ) as ReturnIterableIteratorType<A>;
+    };
+  }
+
+  if (length < 1) {
+    throw new TypeError("'length' must be greater than 1");
+  }
+
+  if (isArray(iterable)) {
+    return arrayDropRight<A>(length, iterable);
+  }
+
+  if (isString(iterable)) {
+    return stringDropRight(length, iterable) as any;
+  }
+
+  if (isIterable<A>(iterable)) {
+    return sync(length, iterable);
+  }
+
+  if (isAsyncIterable<A>(iterable)) {
+    return async(length, iterable);
+  }
+
+  throw new TypeError("'iterable' must be type of Iterable or AsyncIterable");
+}
+
+export default dropRight;
diff --git a/src/Lazy/index.ts b/src/Lazy/index.ts
index b1635c0c..f9a03f67 100644
--- a/src/Lazy/index.ts
+++ b/src/Lazy/index.ts
@@ -8,6 +8,7 @@ import cycle from "./cycle";
 import difference from "./difference";
 import differenceBy from "./differenceBy";
 import drop from "./drop";
+import dropRight from "./dropRight";
 import dropUntil from "./dropUntil";
 import dropWhile from "./dropWhile";
 import entries from "./entries";
@@ -50,6 +51,7 @@ export {
   difference,
   differenceBy,
   drop,
+  dropRight,
   dropUntil,
   dropWhile,
   entries,
diff --git a/src/types/Curry.ts b/src/types/Curry.ts
index 8c55fc47..4c7bb799 100644
--- a/src/types/Curry.ts
+++ b/src/types/Curry.ts
@@ -11,9 +11,10 @@ type Gaps<L extends any[]> = Cast<CleanGaps<{ [K in keyof L]?: L[K] }>, any[]>;
 type Curry<F extends (...args: any[]) => any> = <
   T extends any[],
   G = Drop<Length<T>, Parameters<F>>,
-  R = ReturnType<F>,
 >(
   ...args: Cast<T, Gaps<Parameters<F>>>
-) => G extends [any, ...any[]] ? Curry<(...args: G) => R> : R;
+) => G extends [any, ...any[]]
+  ? Curry<(...args: G) => ReturnType<F>>
+  : ReturnType<F>;
 
 export default Curry;
diff --git a/website/function.json b/website/function.json
index 357f99c7..a2e925af 100644
--- a/website/function.json
+++ b/website/function.json
@@ -8,6 +8,7 @@
     "concurrent",
     "cycle",
     "drop",
+    "dropRight",
     "difference",
     "differenceBy",
     "dropUntil",


Test patch:
diff --git a/test/Lazy/dropRight.spec.ts b/test/Lazy/dropRight.spec.ts
new file mode 100644
index 00000000..709c40c9
--- /dev/null
+++ b/test/Lazy/dropRight.spec.ts
@@ -0,0 +1,134 @@
+import {
+  concurrent,
+  delay,
+  dropRight,
+  filter,
+  map,
+  pipe,
+  toArray,
+  toAsync,
+} from "../../src/index";
+import { Concurrent } from "../../src/Lazy/concurrent";
+
+import { callFuncAfterTime, generatorMock } from "../utils";
+
+describe("dropRightRight", function () {
+  describe("sync", function () {
+    it("should be discarded elements by length", function () {
+      const acc = [];
+      for (const a of dropRight(2, [1, 2, 3, 4, 5])) {
+        acc.push(a);
+      }
+      expect(acc).toEqual([1, 2, 3]);
+    });
+
+    it("should be discarded string by length", function () {
+      const acc = [];
+      for (const a of dropRight(2, "abcde")) {
+        acc.push(a);
+      }
+      expect(acc).toEqual(["a", "b", "c"]);
+    });
+
+    it("should be able to be used as a curried function in the pipeline", function () {
+      const res = pipe(
+        [1, 2, 3, 4, 5, 6, 7, 8],
+        map((a) => a + 10),
+        filter((a) => a % 2 === 0),
+        dropRight(2),
+        toArray,
+      );
+      expect(res).toEqual([12, 14]);
+    });
+  });
+
+  describe("async", function () {
+    it("should be discarded elements by length", async function () {
+      const acc = [];
+      for await (const a of dropRight(2, toAsync([1, 2, 3, 4, 5]))) {
+        acc.push(a);
+      }
+      expect(acc).toEqual([1, 2, 3]);
+    });
+
+    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, 5, 6, 7, 8]),
+        map((a) => a + 10),
+        filter((a) => a % 2 === 0),
+        dropRight(2),
+        toArray,
+      );
+
+      expect(res).toEqual([12, 14]);
+    });
+
+    it("should be discarded elements by length concurrently", async function () {
+      const fn = jest.fn();
+      callFuncAfterTime(fn, 400);
+      const res = await pipe(
+        toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
+        map((a) => delay(100, a)),
+        filter((a) => a % 2 === 0),
+        dropRight(2),
+        concurrent(3),
+        toArray,
+      );
+      expect(fn).toBeCalled();
+      expect(res).toEqual([2, 4, 6]);
+    }, 450);
+
+    it("should be able to handle an error when asynchronous", async function () {
+      await expect(
+        pipe(
+          toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
+          filter(() => {
+            throw new Error("err");
+          }),
+          dropRight(2),
+          toArray,
+        ),
+      ).rejects.toThrow("err");
+    });
+
+    it("should be able to handle an error when working concurrent", async function () {
+      await expect(
+        pipe(
+          toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
+          filter((a) => {
+            if (a === 1) {
+              throw new Error("err");
+            } else return true;
+          }),
+          dropRight(2),
+          concurrent(3),
+          toArray,
+        ),
+      ).rejects.toThrow("err");
+    });
+
+    it("should be able to handle an error when working concurrent - Promise.reject", async function () {
+      await expect(
+        pipe(
+          toAsync([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
+          map((a) => {
+            if (a < 3) return Promise.reject(new Error("err"));
+            else return a;
+          }),
+          dropRight(2),
+          concurrent(3),
+          toArray,
+        ),
+      ).rejects.toThrow("err");
+    });
+
+    it("should be passed concurrent object when job works concurrently", async function () {
+      const mock = generatorMock();
+      const iter = dropRight(2, mock);
+      const concurrent = Concurrent.of(2) as any;
+
+      await iter.next(concurrent);
+      expect((mock as any).getConcurrent()).toEqual(concurrent);
+    });
+  });
+});
diff --git a/test/curry.spec.ts b/test/curry.spec.ts
index 3e4578d3..95f24168 100644
--- a/test/curry.spec.ts
+++ b/test/curry.spec.ts
@@ -1,4 +1,4 @@
-import { curry } from "../src";
+import { curry, filter, map, pipe, toArray } from "../src";
 
 describe("curry", function () {
   it("should not change a unary function", function () {
@@ -17,4 +17,21 @@ describe("curry", function () {
     expect(curried()(1, 2, 3)).toBe(6);
     expect(curried(1)()(2, 3)).toBe(6);
   });
+
+  it("curried function should be able to be used within the pipeline.", function () {
+    const add = curry((a: number, b: number, c: number) => a + b + c);
+    const multiply = curry((a: number, b: number) => a * b);
+    const lt = curry((l: number, r: number) => l < r);
+
+    expect(pipe(1, add(2, 3), multiply(10))).toBe(60);
+    expect(
+      pipe(
+        [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
+        map(add(2, 3)),
+        map(multiply(10)),
+        filter(lt(100)),
+        toArray,
+      ),
+    ).toEqual([110, 120, 130, 140, 150]);
+  });
 });
diff --git a/type-check/Lazy/dropRight.test.ts b/type-check/Lazy/dropRight.test.ts
new file mode 100644
index 00000000..4122693b
--- /dev/null
+++ b/type-check/Lazy/dropRight.test.ts
@@ -0,0 +1,20 @@
+import * as Test from "../../src/types/Test";
+import { toAsync, dropRight, pipe } from "../../src";
+
+const { checks, check } = Test;
+
+const res1 = dropRight(2, []);
+const res2 = dropRight(2, [1, 2, 3, 4]);
+const res3 = dropRight(2, "abcd");
+const res4 = dropRight(2, toAsync([1, 2, 3, 4]));
+const res5 = pipe([1, 2, 3, 4], dropRight(2));
+const res6 = pipe(toAsync([1, 2, 3, 4]), dropRight(2));
+
+checks([
+  check<typeof res1, IterableIterator<never>, Test.Pass>(),
+  check<typeof res2, IterableIterator<number>, Test.Pass>(),
+  check<typeof res3, IterableIterator<string>, Test.Pass>(),
+  check<typeof res4, AsyncIterableIterator<number>, Test.Pass>(),
+  check<typeof res5, IterableIterator<number>, Test.Pass>(),
+  check<typeof res6, AsyncIterableIterator<number>, Test.Pass>(),
+]);
diff --git a/type-check/curry.test.ts b/type-check/curry.test.ts
index 3fac72c9..fc7ea486 100644
--- a/type-check/curry.test.ts
+++ b/type-check/curry.test.ts
@@ -1,7 +1,7 @@
 import * as Test from "../src/types/Test";
 import Curry from "../src/types/Curry";
 import { curry, pipe, toAsync } from "../src";
-import { filter } from "../src/Lazy";
+import { filter, map } from "../src/Lazy";
 
 const { checks, check } = Test;
 
@@ -15,9 +15,13 @@ const res3 = res2(true);
 const res4 = res2(false, 1, 2, 3);
 
 const strictEq = curry(<T>(left: T, right: T) => left === right);
+const add = curry((a: number, b: number) => a + b);
 
 const res5 = pipe([1, 1, 1, 2, 2, 2], filter(strictEq(2)));
 const res6 = pipe([1, 1, 1, 2, 2, 2], toAsync, filter(strictEq(2)));
+const res7 = pipe(1, add(2), add(3));
+const res8 = pipe([1, 2, 3], map(add(10)), map(add(100)));
+const res9 = pipe([1, 2, 3], toAsync, map(add(10)), map(add(100)));
 
 checks([
   check<
@@ -34,4 +38,7 @@ checks([
   check<typeof res4, string, Test.Pass>(),
   check<typeof res5, IterableIterator<number>, Test.Pass>(),
   check<typeof res6, AsyncIterableIterator<number>, Test.Pass>(),
+  check<typeof res7, number, Test.Pass>(),
+  check<typeof res8, IterableIterator<number>, Test.Pass>(),
+  check<typeof res9, AsyncIterableIterator<number>, Test.Pass>(),
 ]);