Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: allow generics to be passed into function type annotation #32973

Closed
5 tasks done
OliverJAsh opened this issue Aug 19, 2019 · 15 comments
Closed
5 tasks done

Suggestion: allow generics to be passed into function type annotation #32973

OliverJAsh opened this issue Aug 19, 2019 · 15 comments
Labels
Duplicate An existing issue was already created

Comments

@OliverJAsh
Copy link
Contributor

OliverJAsh commented Aug 19, 2019

Search Terms

generic function expression annotation annotate

Suggestion

Related: #27124

Given a generic function type,

type Fn<T> = (t: T) => T;

… if we want to use that type as an annotation, there is no way of preserving/propagating the generic—the generic must be provided.

// This works:
const fn: Fn<string> = string => string;

// But what if I want to propagate the generic, so that `fn` is also a generic function?
// There is no syntax for that.

// Pseudo code:
// const fn: Fn<T> = t => t;

// const result1 = fn(1) // number
// const result2 = fn('foo') // string

In Elm, we can do this by passing generics into the function type annotation:

type alias Fn t = t -> t
fn : Fn t
fn t = t

result1 = fn 1
result2 = fn 'foo'

I would like to be able to do a similar thing in TypeScript.

As for what the syntax should actually look like, here's a quick (and possibly very naive) idea:

const fn: <T>Fn<T> = t => t;

Although we would of course have to question how this annotation syntax should work for values other than functions, which can not be generic.

Use Cases

Any time we want to use a function type as an annotation, but keep the resulting type generic.

E.g. a React HOC type, which is generic, and when we use it to define our HOC, we want the resulting HOC type to also be generic:

import { ComponentType } from 'react';

type HOC<P, P2> = (
    ComposedComponent: ComponentType<P>,
) => ComponentType<P2>;

// Ideally we could somehow re-use the `HOC` function type:
// declare const myHoc: <P> HOC<P>

// But we can't, so we have to write out the whole of the `HOC` type, inline in the function definition:
declare const myHoc: <P>(ComposedComponent: ComponentType<P>) => ComponentType<P>;

Another example: generic React components:

import * as React from 'react';

type Props<T> = { t: T };

// Ideally we could somehow re-use the `React.FC` function type:
// declare const MyComponent: <T> FC<Props<T>>;

// But we can't, so we have to write out the whole of the `FC` type, inline in the function definition:
const MyComponent = <T>(props: Props<T>): ReactElement<any> | null =>
  <div>Hello, World!</div>

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.
@OliverJAsh
Copy link
Contributor Author

Another idea: provide syntax to signify the annotation should be treated only as an upper bound:

const fn: SubType<Fn<any>> = <T>(t: T) => t;

Although you still have to awkwardly place the generic in the function definition. 👎

This syntax would not just be limited to function values either:

type X = { foo: number }
const myX: SubType<X> = { foo: 1, bar: 2 };
myX.bar // number

@AnyhowStep
Copy link
Contributor

AnyhowStep commented Aug 19, 2019

You want to be able to use one type to describe these two cases?

//Non-generic type, generic function
type GenericFn = <T>(t : T) => T;
//Generic type, non-generic function
type ConcreteFn<T> = (t : T) => T;

One more case for no real reason,

//Generic type, generic function
type GenericFn2<U> = <T>(t : T) => U;

@OliverJAsh
Copy link
Contributor Author

I'm not sure that's what I'm describing, but maybe I'm just being slow.

@AnyhowStep
Copy link
Contributor

Your type,

type Fn<T> = (t: T) => T;

is the ConcreteFn<T> type,

//Generic type, non-generic function
type ConcreteFn<T> = (t : T) => T;

Your type,

// But what if I want to propagate the generic, so that `fn` is also a generic function?
// There is no syntax for that.
// Pseudo code:
// const fn: Fn<T> = t => t;

is the GenericFn type,

//Non-generic type, generic function
type GenericFn = <T>(t : T) => T;

At least, if I am reading it right.


And you're looking for a way to represent the above two types using just one type.

@OliverJAsh
Copy link
Contributor Author

I don't see that as related.

I have a single generic type:

type Fn<T> = (t: T) => T;

… which I want to use to annotate a value, fn.

However, I don't want to provide a generic to Fn when I'm defining fn—I want fn to be generic and pass its generic through to the Fn annotation.

// Pseudo code:
const fn: Fn<T> = t => t;

const result1 = fn(1) // number
const result2 = fn('foo') // string

@iansan5653
Copy link

In other words, given:

type Fn<T> = (t: T) => T;
const fn: (<T>(t: T) => T) = (t) => t;

you're looking for a way to represent fn in terms of Fn.

@AnyhowStep
Copy link
Contributor

AnyhowStep commented Aug 19, 2019

So, you have a single generic type,

//Generic type, non-generic function
//Same as: type Fn<T> = (t: T) => T;
type ConcreteFn<T> = (t : T) => T;

And you want to write the following,

//Non-generic type, generic function
//Same as your pseudo code: Fn<T>
type GenericFn = <T>(t : T) => T;

const fn: GenericFn = t => t;

const result1 = fn(1) // number
const result2 = fn('foo') // string

However, instead of doing it as above, which requires two type aliases,
you want to be able to use one type alias and one syntactic sugar (to generate the other type alias),

const fn: MagicallyConvertTo_NonGenericType_GenericFunction<ConcreteFn> = t => t;

const result1 = fn(1) // number
const result2 = fn('foo') // string

[Edit]
ninja'd by @iansan5653

@iansan5653
Copy link

@OliverJAsh if I'm not mistaken, the second code block in @AnyhowStep's comment is exactly what you are looking for. You just need to change the definition of Fn and you can use it the way you want:

type Fn = <T>(t: T) => T;

@jack-williams
Copy link
Collaborator

Looks like you want this #17574.

@RyanCavanaugh RyanCavanaugh added the Duplicate An existing issue was already created label Aug 21, 2019
@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

1 similar comment
@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@AnyhowStep
Copy link
Contributor

@RyanCavanaugh TS bot is borked

@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

1 similar comment
@typescript-bot
Copy link
Collaborator

This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.

@jack-williams
Copy link
Collaborator

Putting the bot out of its misery.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Duplicate An existing issue was already created
Projects
None yet
Development

No branches or pull requests

6 participants