microsoft / microsoft/TypeScript
Idiomatic HKTs and Variadic Generic Types
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 111k
- Forks
- 14.4k
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 117
Description
Suggestion
Pertaining to the discussion in #1213 and having looked at similar suggestions (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:
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This feature would agree with the rest of TypeScript's Design Goals.
⭐ Suggestion
Higher Kinded Types
Suggested syntax:
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)
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
type ArrayOfAsyncIterables = WrappedArray<AsyncIterable>;
// == type ArrayOfAsyncIterables<T> = WrappedArray<AsyncIterable><T>;
Variadic Generic Types
Suggested syntax:
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
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 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 the package exposes.
Below code was my attempt at a general currying overload implementation for the package:
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:
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[]
💻 Use Cases
Using this proposal, the example described in the previous section can be covered:
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
}
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
No implementation files, entry points, or tests are named. Start by reviewing the discussion in #1213 and the linked higher-kinded-type suggestions, then determine whether the proposed HKT and variadic generic syntax fits the type-system design. Done would require an accepted design plus compiler and test changes, but the issue does not define those locations.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 15/100