Repository: TypeScript
Issue: Idiomatic HKTs and Variadic Generic Types
Labels: Suggestion,Needs Proposal

# Suggestion

Pertaining to the discussion in #1213 and having looked at [similar suggestions](https://github.com/microsoft/TypeScript/issues?q=is%3Aissue+is%3Aopen+label%3ASuggestion+higher+kinded+types) (borrowing ideas where appropriate), this is a strawman proposal (syntax only) on **HKTs** and **Variadic Generic Types** that _**adheres to existing syntax and mindset in JS/TS**_ , hence named **"idiomatic"**.

## 🔍 Search Terms

HKT
Higher Order Types
Higher Kinded Types
Higher Order Type Functions
Higher Order Function Generic
Variadic Generic Types

## ✅ Viability Checklist

My suggestion meets these guidelines:

* [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
* [x] This wouldn't change the runtime behavior of existing JavaScript code
* [x] This could be implemented without emitting different JS based on the types of the expressions
* [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
* [x] This feature would agree with the rest of [TypeScript's Design Goals](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals).


## ⭐ Suggestion

### Higher Kinded Types

Suggested syntax:

```ts
type HKT<S><T><V> = S<T<V>>;
```

#### Properties

* Positional clarity, same syntax for definition and invocation, visually similar to HOF calls: `f(x)(y)(z)`
```ts
type WrappedArray<S><T> = S<T>[];
type ArrayOfPromises<T> = WrappedArray<Promise><T>;
// With a glance at the definition, we'll know that Promise is
// positionally assigned to S when invoked
```
* Spontaneous logic: more to the left => higher the kind (again similar to HOFs)
* Omission of lower kind = implicit inclusion
```ts
type ArrayOfAsyncIterables = WrappedArray<AsyncIterable>;
// == type ArrayOfAsyncIterables<T> = WrappedArray<AsyncIterable><T>;
```

### Variadic Generic Types

Suggested syntax:

```ts
function aFunction<...T>(...args) {
  return anotherFunction<...T>(...args);  
}
```

#### Properties

* Reuses spread operator, same as in Variadic Tuple Types, exact match with how parameters are spread in JS
* Paves the way for generic inference
```ts
type Generics<F extends (...args) => any> = F extends <...infer G>(...args) => any
  ? G
  : never;
```

## 📃 Motivating Example

I was looking at [the code in the `streaming-iterables`](https://github.com/reconbot/streaming-iterables/blob/8a1e0c0d4e1db1b0d7e62cbd2df581fd86dd2509/lib/map.ts#L11) package, the pattern of defining an `_fn` and then overloading it with an `fn` including a curried version seems a case of **DRY**, happens in [all functions](https://github.com/reconbot/streaming-iterables/tree/8a1e0c0d4e1db1b0d7e62cbd2df581fd86dd2509/lib) the package exposes.

Below code was my attempt at a general currying overload implementation for the package:

```ts
type Leading<T extends any[]> = T extends [...infer I, infer _] ? I : never;
type Last<T extends any[]> = T extends [...infer _, infer I] ? I : never;

function curried<F extends (...args) => any>(
  fn: F
): {
  (...args: Parameters<F>): ReturnType<F>;
  (...args: Leading<Parameters<F>>): (curried: Last<Parameters<F>>) => ReturnType<F>;
} {
  return (...args) =>
    args.length == fn.length - 1
      ? curried => fn(...[...args, curried]) 
      : fn(...args);
}
```

This works well until `generics` come into play:

```ts
function a<T>(b: string, c: number, d: T[]) {
  return [b, c, ...d];
}

const f = curried(a);

f('hi', 42, [1, 2, 3]) // d: unknown[], expected: number[]
f('hi', 42) // curried: unknown[], expected: T[]
```

[SO Question](https://stackoverflow.com/questions/68761267/capture-generic-type-of-passed-function-for-overloads-in-typescript)

## 💻 Use Cases

Using this proposal, the example described in the previous section can be covered:

```ts
function curried<F extends (...args) => any>(
  fn: F
): {
  <...T extends Generics<F>>(...args: Parameters<F><...T>): ReturnType<F><...T>;
  <...T extends Generics<F>>(...args: Leading<Parameters<F><...T>>):
    (curried: Last<Parameters<F><...T>>) => ReturnType<F><...T>;
} {
  return // same as before
}
```