Skip to content

Declare a function as a type; or, defer resolving generic types when declaring a variable as a type #29613

Closed
@MattiasMartens

Description

@MattiasMartens

Search Terms

  • functions
  • higher-order functions
  • generic types
  • type inference
  • type parameters
  • infer

Suggestion

This comprises two suggestions in one as I want to leave it up you to decide which is better value-for-effort. The suggestions are also compatible with each other.

1: typing a function

When declaring a function, I want to be able to declare the type of the function as a whole, not just the type of the parameters or the return value. I want this to be compatible with generic types just like the existing function declaration syntax is.

Example:

type MyStupidType<A, B> = (arg: A, transform: (sameArg: A) => B) => B

function myStupidFunction(arg, transform) {
  return transform(arg);
}: MyStupidType<A, B>;

The above is idiomatic, but clunky, so an alternative suggested syntax is:

<MyStupidType<A, B>> function myStupidFunction(arg, transform) {
  return transform(arg);
}

Since the function may not even have to reference the generic types explicitly (as in the example), it may even be possible to drop them, like so:

<MyStupidType> function myStupidFunction(arg, transform) {
  return transform(arg);
}

Or like so:

<MyStupidType<>> function myStupidFunction(arg, transform) {
  return transform(arg);
}

Or like so:

<MyStupidType<?, ?>> function myStupidFunction(arg, transform) {
  return transform(arg);
}

2: typing a const generically

The usual workaround for the problem I'm facing is to use arrow functions instead:

const myStupidFunction: MyStupidType<any /* ;( */, (sameArg: any) => any /* ;( */> = (arg, transform) => transform(arg)

As you can see, this doesn't work with generics, because I have to make the function non-generic in order to declare it in this way.

The proposed change is to allow the const to remain generic using the infer keyword:

const myStupidFunction: MyStupidType<infer A, infer B> = (arg, transform) => transform(arg);

...or, as suggested in 1), just dropping them if they do not need to be directly referenced:

const myStupidFunction: MyStupidType = (arg, transform) => transform(arg);

Use Cases

I ran into the problem described in #9366 while playing with types describing reducers (in an attempt to building up to understanding transducers):

type SimpleReducer<T, V> = (acc: V, next: T) => V;

type AbstractReduce<T, V> = (
  iterable: Iterable<T>,
  reducer: SimpleReducer<T, V>,
  initialValue: V,
) => V;

const reduceIterable: AbstractReduce = (
  iterable,
  reducer,
  initialValue,
) => {
  let out = initialValue;

  for (let next of iterable) {
    out = reducer(out, next);
  }

  return out;
};

This of course doesn't work because AbstractReduce is being referenced with no type parameters. But my implementation here is still too abstract for me to want to supply them.

However, this works:

function reduceIterableFn<T, V>(
  iterable: Iterable<T>,
  reducer: SimpleReducer<T, V>,
  initialValue: V
): V {
  let out = initialValue;

  for (let next of iterable) {
    out = reducer(out, next);
  }

  return out;
}

This would be fine except that it means I can't use my interface, because there's no way to tell TypeScript that a function implements a generic interface (as opposed to its parameters or return type considered individually).

The overall problem of deferring resolution of generics until a function is called turns out to be extremely difficult under the constraints of TypeScript; but what I'm proposing (hopefully) is not and it suffices for my use case.

Examples

The above sections show examples. When a developer writes a function that needs to implement a type, they can use the above syntax to allow TypeScript to extend the current behaviour around function declarations to function declarations that implement types.

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, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

Metadata

Metadata

Assignees

No one assigned

    Labels

    DuplicateAn existing issue was already created

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions