Closed andrewvarga closed 7 years ago
Another thing that would be really useful is to be able to only specify some of type variables with the rest of them being inferred.
Example use case: With reselect you can write stuff like this:
const selector = createSelector(
state => state.a,
state => state.b,
(a, b) => a + b
);
selector({a: 1, b:2}) // 3
We can type it as follows:
type Selector<S, R> = (state: S) => R;
function createSelector<S, T1, T2, R>(
selector1: Selector<S, T1>,
selector2: Selector<S, T2>,
combiner: (arg1: T1, arg2: T2) => R
);
But actually everything except for S
can be inferred from the code. I usually write it like this:
const selector = createSelector(
(state: MyState) => state.a,
(state: MyState) => state.b,
(a, b) => a + b
);
But it would be great if I could only write MyState
once:
const selector = createSelector<MyState, ?, ?, ?>(
state => state.a,
state => state.b,
(a, b) => a + b
);
@aikoven You might be interested in https://github.com/Microsoft/TypeScript/issues/10571 instead. That proposal is more for the other side of the equation, while this one would be for setting the additional generics to = {}
as a default when omitted.
Hi! Just to adjust a proposal:
The short form for omitting actual parameters should be
MyClassWithDefaultGenerics<>
Otherwise we would block #1213 (it would be almost impossible to implement)
@DanielRosenwasser ? @isiahmeadows
I was surprised today to find this wasn't already in TypeScript. Sad for me. And I really don't want the trailing empty <>
.
@Artazor Actually, I disagree. If necessary, you can just use a type alias with a dummy parameter (or two, etc.) for higher-kinded types, and just leave the all-default case require no parameters. Here's a quick overview of what I mean:
type KeyPromise<T extends string | number = string | number> = Promise<T>;
interface Database<P<~> extends PromiseLike<~>> {
query<T>(s:string, args: any[]): P<T>;
}
// Using the default parameter
type KeyDatabase = Database<KeyPromise>;
// Using a specialization (note the unused `T`)
type NumberPromise<T> = KeyPromise<number>;
type NumberDatabase = Database<NumberPromise>;
@isiahmeadows, could you suggest a reasonable behavior for the following type checking result:
interface Wrapper<T> {value:T}
interface IdWrapper<T = {}> extends Wrapper<T> { id: string }
interface Test<W<~> extends Wrapper<~>> {
num: W<number>;
str: W<string>;
}
type T1 = Test<Wrapper>; // ok
// T1 = { num: {value: number}; str: {value: string}}
type T2 = Test<Wrapper2>; // what should be reported here?
Should the second instantiation be rejected?
If we remove defaults on IdWrapper
declaration, then T2 would be constructed without any problems.
And if we add it again, then there an error should be reported: since Wrapper2 used alone should be treated as instantiated final type, instead of expected generic that takes one parameter.
Btw, I do not understand your example. Could you elaborate what signatures are expected for the query
in both examples?
I suspect that the correct behavior is to defer an application of default generic parameters till the moment when exact count of generic parameters will be determined from the context:
In that case if we have an interface that accepts an unary type constructor
interface X<T<~>> { value: T<number> }
and a binary type constructor with two defaults
interface Y<A = string, B = boolean> { a: A, b: B}
then the application should silently convert binary Y<A,B>
to the unary Y<A> = {a:A, b: boolean}
by applying the trailing default parameters:
type XY = X<Y>; /* should be {
value: {
a: number, // due to X
b: boolean // Y's second default parameter
}
} */
@Artazor That was my original intuition with my idea.
And to clarify, the query
signature for my two examples would be this:
// Using the default parameter
interface KeyDatabase extends Database<KeyPromise> {
query<T>(s:string, args: any[]): KeyPromise<T>;
}
// Using a specialization (note the unused `T`)
type NumberPromise<T> = KeyPromise<number>;
interface NumberDatabase extends Database<NumberPromise> {
query<T>(s:string, args: any[]): KeyPromise<number>; // i.e. NumberPromise<T>
}
Generic parameter defaults will make the implementation of https://github.com/Microsoft/TypeScript/issues/1213 and https://github.com/Microsoft/TypeScript/issues/9949 considerably harder. They seem challenging at presents, so it doesn't help.
Defaults are a source of ambiguities as well. Adding a default may make a function backward incompatible. E.g.
declare function c<A>(f: (x: A) => A, g: (a: A) => A): A;
declare function f<T = number>(x: T): T;
declare function g<T = string>(x: T): T;
c(f, g) // A = string / number or T?
Better type inference and type aliases are more obvious and consistent solutions since defaults help only in the case envisioned by the library/typings author and are opaque to the code maintainers.
The only true benefit of defaults is generifying existing typings (as pointed out by @blakeembrey's example) but I'd much rather see real generics first..
@gcnew
The only true benefit of defaults is generifying existing typings (as pointed out by @blakeembrey's example) but I'd much rather see real generics first.
If nested types make it, then this would all of a sudden become far more interesting. You could make a React type namespace, so you could properly type the library, including raw element references, and ensure that React Native-specific components can't be erroneously used in DOM-specific or renderer-independent components. That being simply by using a generic React type instead. It's an extra layer of verification that would allow much more thorough typing.
declare function c<A>(f: (x: A) => A, g: (a: A) => A): A; declare function f<T = number>(x: T): T; declare function g<T = string>(x: T): T; c(f, g) // A = string / number or T?
That would be a good question, though.
@RyanCavanaugh Are you guys planning to add a milestone to this guy? Looks like you had the spec (and implementation?) mostly fleshed out.
@bcherny work in progress at #13487. I would expect this to be in 2.2 but may slip to 2.3 if we need a few more revisions on the PR.
Awesome, thanks for the excellent work @RyanCavanaugh!
👍 Thanks. I've been wating a long time.
It would be really useful if I could set default values for my generic type variables. In many cases, the users of my class don't want to use a different value and so it's cumbersome to having to type it in.
If I could do this:
then the people who don't care about the type of
userData
(because they don't use it) and the people who are fine with it being aString
don't have to set a generic type but others can if they want.Also, these type variables could default to
any
or the type they extend (if they do) without explicitly defaulting them. For example:<T extends MyClass>
would default toMyClass
(without explicitly writing it) and<T>
could default toany
What do you think?