Repository: gcanti/fp-ts (ts)

Issue:
Use tuple as function arguments
## 🚀 Feature request

### Current Behavior

If you want to pass a tuple as function arguments you have to deconstruct the tuple, but miss point free syntax.

```typescript
declare const f: (a: A, b: B) => C;

pipe(
  tuple(a, b),
  x => f(...x),
);
```

### Desired Behavior

Use function `apT` to resolve this

```typescript
declare const f: (a: A, b: B) => C;

// apT will apply the tuple as function arguments
const fT: (t: [A, B]) => C = apT(f);

pipe(
  tuple(a, b),
  fT,
);
```

### Suggested Solution

This function could be implemented as

```typescript
const apT: <A extends Array<unknown>, B>(f: (...a: A) => B) => (a: A) => B =
    f => a => f(...a);
```

### Your environment

Every OS

| Software   | Version(s) |
| ---------- | ---------- |
| fp-ts      | 2.2.0        |
| TypeScript | 3.7.2           |


Pull request description:
add tupled / untupled functions, closes #1062
/cc @frndmg

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26cf0caf..8d8ae630 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,8 @@ high state of flux, you're at risk of it changing without notice.
     - add `chainEither`, `chainIOEither`, `chainTaskEither` (@gcanti)
   - `StateReaderTaskEither`
     - add `chainEither`, `chainIOEither`, `chainTaskEither`, `chainReaderTaskEither` (@gcanti)
+  - `function`
+    - add `tupled`, `untupled` functions, closes #1062 (@gcanti)
 - **Deprecation**
   - `Ord`
     - deprecate `getSemigroup` in favor of `getMonoid` (@gcanti)
diff --git a/docs/modules/function.ts.md b/docs/modules/function.ts.md
index 290c6d3b..fd953571 100644
--- a/docs/modules/function.ts.md
+++ b/docs/modules/function.ts.md
@@ -32,6 +32,8 @@ Added in v2.0.0
 - [increment (function)](#increment-function)
 - [not (function)](#not-function)
 - [tuple (function)](#tuple-function)
+- [tupled (function)](#tupled-function)
+- [untupled (function)](#untupled-function)
 
 ---
 
@@ -338,3 +340,37 @@ export function tuple<T extends Array<any>>(...t: T): T { ... }
 ```
 
 Added in v2.0.0
+
+# tupled (function)
+
+Creates a tupled version of this function: instead of `n` arguments, it accepts a single tuple argument.
+
+**Signature**
+
+```ts
+export function tupled<A extends Array<unknown>, B>(f: (...a: A) => B): (a: A) => B { ... }
+```
+
+**Example**
+
+```ts
+import { tupled } from 'fp-ts/lib/function'
+
+const add = tupled((x: number, y: number): number => x + y)
+
+assert.strictEqual(add([1, 2]), 3)
+```
+
+Added in v2.4.0
+
+# untupled (function)
+
+Inverse function of `tupled`
+
+**Signature**
+
+```ts
+export function untupled<A extends Array<unknown>, B>(f: (a: A) => B): (...a: A) => B { ... }
+```
+
+Added in v2.4.0
diff --git a/dtslint/ts3.5/index.ts b/dtslint/ts3.5/index.ts
index 2f15288d..b00a155e 100644
--- a/dtslint/ts3.5/index.ts
+++ b/dtslint/ts3.5/index.ts
@@ -177,20 +177,14 @@ interface fromNullableTest2 {
 }
 declare const fromNullableTest3: <Key extends keyof fromNullableTest2>(key: Key) => fromNullableTest2[Key]
 // $ExpectType Option<number>
-Fu.flow(
-  fromNullableTest3,
-  O.fromNullable
-)('foo')
+Fu.flow(fromNullableTest3, O.fromNullable)('foo')
 
 //
 // Either
 //
 
 // $ExpectType Either<string, number>
-Fu.flow(
-  fromNullableTest3,
-  E.fromNullable('error')
-)('foo')
+Fu.flow(fromNullableTest3, E.fromNullable('error'))('foo')
 
 //
 // HKT
@@ -375,10 +369,13 @@ Fu.tuple(1, 'a') // $ExpectType [number, string]
 Fu.tuple(1, 'a', true) // $ExpectType [number, string, boolean]
 
 // $ExpectType <A>(head: A, tail: A[]) => Option<A>
-Fu.flow(
-  A.cons,
-  A.head
-)
+Fu.flow(A.cons, A.head)
+
+// tupled
+Fu.tupled(A.insertAt) // $ExpectType <A>(a: [number, A]) => (as: A[]) => Option<A[]>
+
+// untupled
+Fu.untupled(Fu.tupled(A.insertAt)) // $ExpectType <A>(i: number, a: A) => (as: A[]) => Option<A[]>
 
 //
 // Filterable overloads
diff --git a/src/function.ts b/src/function.ts
index b4737351..3391c343 100644
--- a/src/function.ts
+++ b/src/function.ts
@@ -273,3 +273,28 @@ export function decrement(n: number): number {
 export function absurd<A>(_: never): A {
   throw new Error('Called `absurd` function which should be uncallable')
 }
+
+/**
+ * Creates a tupled version of this function: instead of `n` arguments, it accepts a single tuple argument.
+ *
+ * @example
+ * import { tupled } from 'fp-ts/lib/function'
+ *
+ * const add = tupled((x: number, y: number): number => x + y)
+ *
+ * assert.strictEqual(add([1, 2]), 3)
+ *
+ * @since 2.4.0
+ */
+export function tupled<A extends Array<unknown>, B>(f: (...a: A) => B): (a: A) => B {
+  return a => f(...a)
+}
+
+/**
+ * Inverse function of `tupled`
+ *
+ * @since 2.4.0
+ */
+export function untupled<A extends Array<unknown>, B>(f: (a: A) => B): (...a: A) => B {
+  return (...a) => f(a)
+}


Test patch:
diff --git a/test/function.ts b/test/function.ts
index 45c24e79..5d5263df 100644
--- a/test/function.ts
+++ b/test/function.ts
@@ -12,7 +12,9 @@ import {
   not,
   unsafeCoerce,
   absurd,
-  flow
+  flow,
+  tupled,
+  untupled
 } from '../src/function'
 
 const f = (n: number) => n + 1
@@ -78,4 +80,22 @@ describe('function', () => {
     assert.deepStrictEqual(flow(f, g, f, g, f, g, f, g)(2), 62)
     assert.deepStrictEqual(flow(f, g, f, g, f, g, f, g, f)(2), 63)
   })
+
+  it('tupled', () => {
+    const f1 = (a: number): number => a * 2
+    const f2 = (a: number, b: number): number => a + b
+    const u1 = tupled(f1)
+    const u2 = tupled(f2)
+    assert.deepStrictEqual(u1([1]), 2)
+    assert.deepStrictEqual(u2([1, 2]), 3)
+  })
+
+  it('untupled', () => {
+    const f1 = (a: [number]): number => a[0] * 2
+    const f2 = (a: [number, number]): number => a[0] + a[1]
+    const u1 = untupled(f1)
+    const u2 = untupled(f2)
+    assert.deepStrictEqual(u1(1), 2)
+    assert.deepStrictEqual(u2(1, 2), 3)
+  })
 })