bind() returns type 'any' and does not type check it's arguments. e.g.
var add = function (a: number, b: number) { return a + b; };
var addString = add.bind(null, 'hello');
compiles without error and 'addString' has type any.
bind is a default way to achieve partial application so it would be great to have it typechecked properly.
Similarly for apply, the code
var listA = [1, 2, 3];
var listB = ['a', 'b', 'c'];
listA.push.apply(listA, listB);
compiles without error and listA is still incorrectly marked as type 'number[]'.
call() example:
var add5 = function (a: number) { return 5 + a; };
add5.call(null, 'a string');
Again without a compiler error.
It would be nice to type these more strongly but we will need a proposal for some options. We previously discussed this in some early design meetings on generics and weren't really able to come up with something suitable. At least using Function instead of 'any' where applicable might be a meaningful improvement. See Ander's response here for a similar example: https://typescript.codeplex.com/discussions/462819
This will return you a function with the same signature you passed in--and for memoize that's really the thing that matters. There's no generic way to extract the parameters and/or return type from a function type, so the hashFn parameter is probably best left as just Function.
With regards to 'bind' and 'call', what specifically do you need proposals for? Are there cases where the returned type could be ambiguous? Apologies if I'm missing something obvious.
Just a brief exploration of some of the issues here. We would need to fill in all the ?
s along with descriptions of how those results would be achieved.
interface fn {
(): string;
(n: number): void;
(n: number, m: number): Element;
(k: string): Date;
}
var x: fn;
var y = x(); // y: string
var args = [3]; // args: number[]
var c1 = x.apply(window, args); // c1: ?
args = []; // arg: still number[]
var c2 = x.apply(window, args); // c2: ??
var b = x.bind(undefined, null); // b: what type?
var j = b(); // j: ?
call
is less difficult to reason about, but it's probably the most unlikely to be used in a place where you'd need strong type information.
It seems like the apply
method of a function just cannot return a typed value.
In order to determine the type we have to find a mapping of an array (arguments) to a number of tuples (parameters of different signatures from overloads). It can only be done if we know the number of elements in the array and their types. Having these 2 things we can view the array as a tuple. Matching tuples to tuples is a solvable problem. Now in order to know the number of elements in an array and their type at the compile time it has got to be an array literal (not an expression). Only in this case we can deduce the type, but restricting apply
to array literals only would be a significant and pointless limitation. All this means that in order to have a typed version of apply there has to be a different syntax that takes array literals, or just tuples straight, and deduces the type of the result. In order not to introduce new syntax there can be a typed overload of apply
that takes an array literal or just a list of arguments as they listed in the original function. Even when the original signature of a function matches the one of the apply
method of that function it should not be a problem. In this case there will be no overloads, but just one single way to call it which is what it has now.
// typescript
f(a: number, b: string) {}
f.apply(<any>null, 10, 'hey');
// emitted javascript
f(a, b) {}
f.apply(null, [10, 'hey'])
Here is another example I came across where this is an issue:
interface Entity {}
class Example {
constructor( private lines: string[] ) {}
mapLines(): Entity[][] {
return this.lines.map( this.mapLine.bind( this ) );
}
mapLine( line: string ): Entity[] {
return line.split( '' ).map( this.mapCharacter.bind( this ) );
}
mapCharacter( character: string ): Entity {
return {};
}
}
var instance = new Example( ["foo", "baz"] );
instance.mapLines();
The bind
s are needed because of the usage of this
, but since bind
returns any
, the compiler will error out because of the mismatch of Entity{}[]
and Entity[][]
.
The only solution I could think of is to force the type:
return <Entity[][]>this.lines.map( this.mapLine.bind( this ) );
@RyanCavanaugh
regarding:
call
is less difficult to reason about, but it's probably the most unlikely to be used in a place where you'd need strong type information.
I find I have a lot of trouble with this issue, as I frequently factor my functions so that they contain small helper functions. Here's a simplified example to show the structure I use:
interface Options {a: string;}
class A {
local: string;
f(options: Options) {
function helper(options: Options) {
this.local = options.a;
}
helper.call(this, {b: ''}); // the error here is not reported by the compiler
}
}
Any helpers that reference this must be invoked via call().
This problem hits me hard when I refactor code, including renaming variables, which I do frequently as I come up with better naming.
I would greatly appreciate it if you could add support for type checking the arguments of a call().
(I also understand that apply() has too many ambiguities to support type checking).
EDITED: to add type helper
@psnider, if you want to avoid introducing a lexical this
, you could explicitly capture the outer one
var _this = this;
function helper(opts: Options) {
_this.a = // ...
}
or you could also use an arrow function.
var helper = (opts: Options) => {
this.a = // ...
}
@DanielRosenwasser
awesome! Cleans things up nicely, solving two problems:
Thanks, I had overlooked using the fat-arrow syntax here,
although I use it all the time for callbacks and promises.
I can continue on with my refactoring addiction without fear!
For reference, the modified code, for which the compiler correctly reports the type error is:
interface Options {a: string;}
class A {
local: string;
f(options: Options) {
var helper = (options: Options) => {
this.local = options.a;
}
helper(this, {b: ''});
}
}
Ok how about this as a spec for bind:
Binding with:
results in compiler error: 'Supplied parameters do not match any signature of call target.' e.g.
var x: (a: typeA, b: typeB, c: typeC): returnType;
var a: typeA;
var b: typeB;
var c: typeC;
var d: typeD;
var boundX = x.bind(undefined, a, b, d); // COMPILER ERROR
var boundX' = x.bind(undefined, a, b, c, d); // COMPILER ERROR
For an overloaded function where there is ambiguity, 'bind' should return type any. e.g.
interface fn {
(): string;
(n: number): void;
(n: number, m: number): Element;
(k: string): Date;
}
var x: fn;
var boundX = x.bind(undefined, null); // boundX: any
For:
bind returns the expected typed value:
var x: (a: typeA, b: typeB, c: typeC): returnType;
var a: typeA;
var b: typeB;
var boundX = x.bind(undefined, a); // boundX: (b: typeB, c: typeC): returnType;
var boundX' = x.bind(undefined, a, b); // boundX': (c: typeC): returnType;
interface fn {
(): string;
(n: number): void;
(k: string): Date;
}
var y: fn;
var boundY = y.bind(undefined, 5); // boundY: (): void
var boundY' = y.bind(undefined, 'a string'); // boundY': (): Date
@psnider glad that solves your problem!
Now that we have tuple types, my personal opinion is that instead of trying to add magic to the typing for these functions, you should actually use typed functions.
var _apply = (f: any, args: any[], thisArg?: any) => f.apply(thisArg, args);
var apply$: <T, R>(f: (...values: T[]) => R, args: T[], thisArg?: any) => R = _apply
var apply2: <T1, T2, R>(f: (a1: T1, a2: T2) => R, args: [T1, T2], thisArg?: any) => R = _apply
var apply3: <T1, T2, T3, R>(f: (a1: T1, a2: T2, a3: T3) => R, args: [T1, T2, T3], thisArg?: any) => R = _apply;
var apply4: <T1, T2, T3, T4, R>(f: (a1: T1, a2: T2, a3: T3, a4: T4) => R, args: [T1, T2, T3, T4], thisArg?: any) => R = _apply;
var apply5: <T1, T2, T3, T4, T5, R>(f: (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => R, args: [T1, T2, T3, T4, T5], thisArg?: any) => R = _apply;
// etc.
Or you could use overloads if you don't like explicit arities:
function apply<T1, T2, T3, T4, T5, R>(f: (a1: T1, a2: T2, a3: T3, a4: T4, a5: T5) => R, args: [T1, T2, T3, T4, T5], thisArg?: any): R;
function apply<T1, T2, T3, T4, R>(f: (a1: T1, a2: T2, a3: T3, a4: T4) => R, args: [T1, T2, T3, T4], thisArg?: any): R;
function apply<T1, T2, T3, R>(f: (a1: T1, a2: T2, a3: T3) => R, args: [T1, T2, T3], thisArg?: any): R;
function apply<T1, T2, R>(f: (a1: T1, a2: T2) => R, args: [T1, T2], thisArg?: any): R;
function apply<T, R>(f: (...values: T[]) => R, args: T[], thisArg?: any): R;
function apply(f: any, args: any[], thisArg?: any) {
return f.apply(thisArg, args);
}
Instead of bind
, it was already easy enough to write curry*
and bind*
doing something like I did above, or the following:
function curry2<T1, T2, R>(f: (a1: T1, a2: T2) => R) {
return (a1: T1) => (a2: T2) => f(a1, a2);
}
function curry3<T1, T2, T3, R>(f: (a1: T1, a2: T2, a3: T3) => R) {
return (a1: T1) => (a2: T2) => (a3: T3) => f(a1, a2, a3);
}
// etc.
function bind2<T1, T2, R>(f: (a1: T1, a2: T2) => R, thisArg: any) {
return (a1: T1) => (a2: T2): R => f.call(thisArg, a1, a2);
}
// etc.
call*
could be typed in a similar manner.
@DanielRosenwasser Could you expand on why you are against my solution for bind? Do you believe your proposal will be better from a user standpoint?
As a typescript user, I want to use bind in an identical way to javascript. i.e.
myFunction.bind(undefined, arg0, arg1);
and have it typed as per my previous post. Your proposals would not support this. For example, using your solution 'bind2', the code would look like:
bind2(myFunction, undefined)(arg0);
And even worse for functions with more applied args e.g.
bind5(myFunction, undefined)(arg0)(arg1)(arg2)(arg3);
Using bind as described in my proposal is not only more familiar to people who know javascript, but also allows easier transition from untyped javascript code into properly typed typescript.
@jameslong we generally understand what the desired behavior of bind is. Your solution is stating the cases which would be errors and which would not be but that's not really the level of detail holding back this feature. We need a solution that models the type signatures and type flow in a way that allows the compiler to actually figure out whether or not to report an error in these cases.
Thanks @danquirk, that effectively describes the main issue. The other is that I'm generally not a fan of having compiler-internal support of bind
. We could do a lot of compiler-internal stuff, like type-checking static strings in an eval
invocation. In _most_ cases, it's better to have a a fishing net than one really big fish (or something like that, I don't really know anything about fishing).
@jameslong, for your examples, why even bother with bind
?
bind2(myFunction, undefined)(arg0)
becomes something like
(x: number) => myFunction(arg0, x)
and
bind5(myFunction, undefined)(arg0)(arg1)(arg2)(arg3)
becomes something like
(x: string) => myFunction(arg0, arg1, arg2, arg3, x)
It's not perfect, but it's not nearly the worst workaround either. If you need to specify a this
argument, use the apply*
functions I defined above.
@danquirk Thanks Dan. I appreciate there are complexities in the compiler implementation of this feature. However it seems there is not agreement on whether we would like this to be a compiler feature or not which is why I was making that point.
I think in general we'd all agree that any signature that has any
in it would ideally have something more specific. There's certainly differing levels of priority of fixing such signatures depending on their relative use/complexity and the complexity of modeling them more exactly. Your list is certainly useful to have as a reference. At the moment we don't have a technical proposal that gets us far enough to even evaluate how it handles some cases in your list vs others.
apply
and call
could be easily fixed by variadic generic support (syntax only for demonstration, requires this
typing).
interface Function {
apply<T, R, ...X>(
this: (this: T, ...args: X) => R,
thisArg: T,
args: X
): R;
call<T, R, ...X>(
this: (this: T, ...args: X) => R,
thisArg: T,
...args: X
): R;
}
bind
would be a beast to properly type, though, as a compiler would have to properly infer this kind of thing (syntax only for demonstration, requires this
typing for the function version):
interface Function {
// bound functions
bind<T, R, ...X, ...Y>(
this: (this: T, ...initial: X, ...rest: Y) => R,
thisArg: T,
...initial: X
): (...rest: Y) => R;
// bound constructors
bind<C, ...X, ...Y>(
this: new (...initial: X, ...rest: Y) => C,
thisArg: any,
...initial: X
): new (...rest: Y) => C;
}
Let's just say it requires lazy inference of the template each use, probably using a simplified structure along the same lines as Conway's Game of Life or Rule 110, something not currently done, and something not easy to program.
let bar: T;
let baz: A;
let quux: B;
let spam: C;
let eggs: D;
declare function foo(this: T, a: A, b: B, c: C, d: D): E;
let res = foo.bind(bar, baz, quux);
res(spam, eggs)
_Expansion/substitution of last statement:_
// Initial
bind<T, R, ...X, ...Y>(this: (this: T, ...initial: X, ...rest: Y) => R, thisArg: T, ...initial: X): (this: any, ...rest: Y) => R;
// Steps to reduce
bind<T, R, ...X, ...Y>(this: (this: T, ...initial: X, ...rest: Y) => R, thisArg: T, ...initial: X): ( ...rest: Y) => R;
bind<T, E, ...X, ...Y>(this: (this: T, ...initial: X, ...rest: Y) => E, thisArg: T, ...initial: X): ( ...rest: Y) => E;
bind<T, E, A, ...X, ...Y>(this: (this: T, a: A, ...initial: X, ...rest: Y) => E, thisArg: T, a: A, ...initial: X): ( ...rest: Y) => E;
bind<T, E, A, B, ...X, ...Y>(this: (this: T, a: A, b: B, ...initial: X, ...rest: Y) => E, thisArg: T, a: A, b: B, ...initial: X): ( ...rest: Y) => E;
bind<T, E, A, B, ...Y>(this: (this: T, a: A, b: B, ...rest: Y) => E, thisArg: T, a: A, b: B ): ( ...rest: Y) => E;
bind<T, E, A, B, C, ...Y>(this: (this: T, a: A, b: B, c: C, ...rest: Y) => E, thisArg: T, a: A, b: B ): (c: C, ...rest: Y) => E;
bind<T, E, A, B, C, D, ...Y>(this: (this: T, a: A, b: B, c: C, d: D, ...rest: Y) => E, thisArg: T, a: A, b: B ): (c: C, d: D, ...rest: Y) => E;
bind<T, E, A, B, C, D >(this: (this: T, a: A, b: B, c: C, d: D ) => E, thisArg: T, a: A, b: B ): (c: C, d: D ) => E;
// Signature of what's finally type checked
bind<T, E, A, B, C, D>(this: (this: T, A, B, C, D) => E, T, A, B): (C, D) => E;
Not simple. (Not even C++ can do this without major template hacks)
_Or, in highly technical CS jargon, checking the above type correctly may mean controlling the variadic expansion in each occurence via a simple Class 1 cellular automaton, reducing it until the variadic types have been eliminated. There is the possibility that a template expands infinitely, especially if the function is recursive (singly or cooperatively), but a hard limit could prevent a lock-up for the compiler._
And as for this specific issue, it's nearly completely covered by the variadic generic proposal in #1773 and the this
typing proposal in #229 and the large number of other dupes.
related to #3694
For those of you who care, I've create the following little TypeScript-snippet that outputs an overloaded function declaration of 'bind':
Output (you can scale this up if you want):
function bind<A, B, Z>(f: (_0: A, _1: B) => Z, _0: A): (_0: B) => Z;
function bind<A, B, C, Z>(f: (_0: A, _1: B, _2: C) => Z, _0: A): (_0: B, _1: C) => Z;
function bind<A, B, C, Z>(f: (_0: A, _1: B, _2: C) => Z, _0: A, _1: B): (_0: C) => Z;
function bind<A, B, C, D, Z>(f: (_0: A, _1: B, _2: C, _3: D) => Z, _0: A): (_0: B, _1: C, _2: D) => Z;
function bind<A, B, C, D, Z>(f: (_0: A, _1: B, _2: C, _3: D) => Z, _0: A, _1: B): (_0: C, _1: D) => Z;
function bind<A, B, C, D, Z>(f: (_0: A, _1: B, _2: C, _3: D) => Z, _0: A, _1: B, _2: C): (_0: D) => Z;
function bind<A, B, C, D, E, Z>(f: (_0: A, _1: B, _2: C, _3: D, _4: E) => Z, _0: A): (_0: B, _1: C, _2: D, _3: E) => Z;
function bind<A, B, C, D, E, Z>(f: (_0: A, _1: B, _2: C, _3: D, _4: E) => Z, _0: A, _1: B): (_0: C, _1: D, _2: E) => Z;
function bind<A, B, C, D, E, Z>(f: (_0: A, _1: B, _2: C, _3: D, _4: E) => Z, _0: A, _1: B, _2: C): (_0: D, _1: E) => Z;
function bind<A, B, C, D, E, Z>(f: (_0: A, _1: B, _2: C, _3: D, _4: E) => Z, _0: A, _1: B, _2: C, _3: D): (_0: E) => Z;
function bind(f, ...toBind: any[]) {
return f.bind.apply(f, [null].concat(toBind));
}
Snippet:
function range(start: string,stop: string): string[] {
var result: string[] = [];
var index: number;
for (var index = start.charCodeAt(0), end = stop.charCodeAt(0); index <= end; ++index){
result.push(String.fromCharCode(index));
}
return result;
};
function untilIndex<T>(index: number, ts: T[]): T[] {
return ts.slice(0, index);
}
function fromIndex<T>(index: number, ts: T[]): T[] {
return ts.slice(index);
}
function getParameters(genericTypeNames: string[]): string[] {
return genericTypeNames.map((genericTypeName, index) => {
return '_' + index + ': ' + genericTypeName;
});
}
function getParametersAsString(genericTypeNames: string[]): string {
return getParameters(genericTypeNames).join(', ');
}
function getFunctionSignature(genericTypeNames: string[], returnTypeName: string): string {
return '(' + getParametersAsString(genericTypeNames) + ') => ' + returnTypeName;
}
function getBindOverloads(genericTypeNames: string[]): string {
var returnTypeName = 'Z';
var functionToBindSignature: string = getFunctionSignature(genericTypeNames, returnTypeName);
var overloads: string = '';
var index: number;
for (index = 1; index < genericTypeNames.length; ++index) {
var before: string[] = untilIndex(index, genericTypeNames);
var after: string[] = fromIndex(index, genericTypeNames);
var hasBoundParameters: boolean = before.length > 0;
var boundParametersAsString: string = getParametersAsString(before);
var boundFunctionTypeAsString: string = getFunctionSignature(after, returnTypeName);
overloads += 'function bind<' + genericTypeNames.concat([returnTypeName]).join(', ') + '>(' +
'f: ' + functionToBindSignature +
(hasBoundParameters ? (', ' + boundParametersAsString) : '') +
'): '
+ boundFunctionTypeAsString
+ ';<br />';
}
return overloads;
}
function getAllBindOverloads(genericTypeNames: string[]): string {
var result = '';
var index: number;
for (index = 0; index < genericTypeNames.length; ++index) {
result += getBindOverloads(untilIndex(index, genericTypeNames));
}
return result + 'function bind(f, ...toBind: any[]) {<br /> return f.bind.apply(f, [null].concat(toBind));<br />}';
}
document.write(getAllBindOverloads(range('A','F')));
Can this be approached in an incremental way? What I'm interested in the use of bind
for, in most cases, is to avoid wrapping a callback in an arrow function.
interface AngularScope
{
$watch<T>(event:string, callback: (newValue:T, oldValue:T)=>void);
}
class Example
{
constructor($scope: AngularScope){
$scope.$watch('change', this.theChangeCallback.bind(this));
}
theChangeCallback(new: string, old:string) {
// Do work
}
}
In this circumstance, I am not using bind
for partial application. I am using it merely to avoid writing code that looks like this:
$scope.$watch('change', (new:string, old:string)=>this.theChangeCallback(new, old));
In this situation, the type should be (new:string, old:string)=>void
, just as theChangeCallback
is in the class.
What would need to be added to the compiler, just to achieve this? I feel like if bind
were able to take on the exact type of the function it's called from, it would be a start in the right direction. Partial application could be worked in later.
this.theChangeCallback
@bryanerayner you should use an arrow function to define the member in the class from the get go. This is covered here : https://basarat.gitbooks.io/typescript/content/docs/tips/bind.html
@bryanerayner my PR #6739 gives types to apply, call and bind. Take a look in core.d.ts: 5fe84781592a08b5294e01a2fbf42d1def07111d
I should improve the annotation to explicitly mention that this: void
for the bound function.
this should be better now with https://github.com/Microsoft/TypeScript/pull/6739.
The ideal fix would be possible when/if https://github.com/Microsoft/TypeScript/issues/1773 is implemented.
@mhegazy isn't #6739 disabled (maybe only partially)? Its a bit confusing reading that + https://github.com/Microsoft/TypeScript/issues/7689 .
A clear message about what was added will help me read it better :rose:
typescript@next
later tonight. #6739 adds two new features:this
argument to a function or a method. it has to be the first argument in the list.--noImplicitThis
, that makes it illegal to use this
unless defined, either explicitly in a function, e.g function foo(this: Bar) { this }
or implicitly, by virtue of being in a class. Please see https://github.com/Microsoft/TypeScript/issues/6018 for more details.
The comments in https://github.com/Microsoft/TypeScript/issues/7689 are regarding a different proposal that #6739 implemented earlier to prevent tearing off methods of classes and interfaces, as there is an implicit this
assumption about them. that was --strictThis
or --strictThisChecks
@mhegazy thanks! :rose:
@mhegazy and @basarat thanks for bind
, call
and apply
.
How about name: string
(ES6)?
this is defined in https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es6.d.ts#L4201, you get that automatically when you set --target ES6
, or if you are using typescript@next
, you can use --lib es6
@mhegazy I see, but I am using it in a polyfill for --target ES5
(to check if we're 2015-ready) ;) Basically no worries, I could "extend" the interface Function
...
if you are using a polyfill library, it should provide a .d.ts matching what it implements, see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/core-js/core-js.d.ts for an example.
declare function f(): number;
declare function f(s: string): string;
// BUT TypeScript can't use the number of arguments to determine which overload `call` should use.
var n: number = f.call(null);
var s: string = f.call(null, 'foo');
The compiler currently compares the last overload in declaration order, so the return type inferred for f.call
is _always_ string
, never number
.
mhegazy removed this from the TypeScript 2.0 milestone on Apr 28
Oh :(
Is this issue still on the radar?
We thought we had a fix in #6739 that solved it. but then that turned out to be invalid. please see @sandersn reply in https://github.com/Microsoft/TypeScript/issues/212#issuecomment-215488970 .
@mhegazy Does this mean the current state of overload resolution makes it too difficult to implement the feature, or can the work be salvaged? I'm trying to figure out if typed apply
/bind
/call
is still on the table.
this
parameters should allow fully typed apply/bind/call
. However, the prototype definitely doesn't address overload resolution and I don't think the proposal does either.bind should return self type?
@frogcjn only if .bind
is called with a single argument. If it's called with more than one, it changes the function signature.
Just FYI this is how it's handled by Flow 0.32.0:
function func(a: number, b: string) {
console.log([a, b])
}
func.call(null, ['b'])
// 13: func.call(null, ['b'])
// ^^^^^^^^^^^^^^^^^^^^^^ call of method `call`
// 13: func.call(null, ['b'])
// ^^^^^^^^^^^^^^^^^^^^^^ undefined (too few arguments, expected default/rest parameters). This type is incompatible with
// 10: function func(a: number, b: string) {
// ^^^^^^ string
func.call(null, ['b', 1])
// 13: func.call(null, ['b', 1])
// ^^^^^^^^^^^^^^^^^^^^^^^^^ call of method `call`
// 13: func.call(null, ['b', 1])
// ^^^^^^^^ array literal. This type is incompatible with
// 10: function func(a: number, b: string) {
// ^^^^^^ number
FYI: This package is a workaround for this untyped bind()
issue.
https://github.com/teppeis/bind.ts
There was a simple bind
overload by suggested by @jcalz at https://github.com/Microsoft/TypeScript/issues/16676#issuecomment-310128938 that would already improve the current situation a bit for when only the thisArg
is provided:
interface Function {
bind<F extends Function>(this: F, thisArg: any): F
}
For a more general solution, I tried a bind
attempt at https://github.com/Microsoft/TypeScript/issues/5453#issuecomment-309035811, based on that and optionally #6606:
interface Function {
bind<
F extends (this: T, ...args: ArgsAsked) => R,
ArgsAsked extends any[],
R extends any,
T,
Args extends any[], // tie to ArgsAsked
Left extends any[] = DifferenceTuples<ArgsAsked, Args>,
EnsureArgsMatchAsked extends 0 = ((v: Args) => 0)(TupleFrom<ArgsAsked, TupleLength<Args>>)
// ^ workaround to ensure we can tie `Args` to both the actual input params as well as to the desired params. it'd throw if the condition is not met.
>(
this: F,
thisObject: T,
...args: Args
): (this: any, ...rest: Left) => R;
// ^ `R` alt. to calc return type based on input (needs #6606): `F(this: T, ...[...Args, ...Left])`
}
@RyanCavanaugh:
Just a brief exploration of some of the issues here. We would need to fill in all the ?s along with descriptions of how those results would be achieved.
interface fn {
(): string;
(n: number): void;
(n: number, m: number): Element;
(k: string): Date;
}
var x: fn;
var y = x(); // y: string
// original version, where mutation screws everything up:
var args = [3]; // args: number[]
var c1 = x.apply(window, args); // error, not guaranteed to get a matching overload for `number[]`. had there been a `number[]` overload yielding say `Foo`, then probably infer `string | void | Element | Foo`?
args = []; // arg: still number[]
var c2 = x.apply(window, args); // c2: ditto
// without mutation, the only way around it I can see:
const args1 = [3]; // args1 type: [3], following https://github.com/Microsoft/TypeScript/issues/16389
var c1 = x.apply(window, args1); // c1: void
const args2 = []; // args2 type: for this to work this would have to be give the empty tuple type []. is it even possible to expose that?
var c2 = x.apply(window, args2); // c2: string
var b = x.bind(undefined, null); // b type: normally `(...args: Args) => fn(null, ...Args)` based on 5453 + 6606, but with `strictNullChecks` error since having `null` as the first argument would already result in no matching overloads in `fn`.
var j = b(); // j: with `strictNullChecks`: `any` since that's what `b` got due to its error. without `strictNullChecks`: `void`, as it'd just settle for the first unary overload.
@mhegazy Any updates this. Need this for typescript to support currying, partial applications etc. in functional programming.
I wanted these to be strongly typed and found out that it's relatively easy to type, just time consuming. As a result, I made a generator to do it for me. People might have done this previously but I couldn't find anything. Was a fun side project.
Generated types (supports up to 10 arguments): https://gist.github.com/Yuudaari/f4c21f8e5e3dad36e4c7f61cbddb4f22
Generator (for any number of arguments... it's up to you!): https://gist.github.com/Yuudaari/508709f582c4aa76eeea70365d145c93
Replace native methods with ones that support these methods on class constructors: https://gist.github.com/Yuudaari/68662657485524c8f1c65250e7c2d31f
This uses prototype
but you can pretty easily make it not if you so desire.
I really wish there was syntactic sugar for bind
already tho... Even tho it's strongly typed, using bind
still feels a bit hacky/like overkill
@Yuudaari looks like a good candidate for lib.d.ts
:)
@RyanCavanaugh Any interest in merging in massively overloaded typings for apply
, bind
, and call
(like what @Yuudaari wrote up) as an improvement until variadic types are in?
@bcherny There are caveats to it; using the methods on varargs functions falls back to the provided function overloads, currently, whereas if there were just my overloads, it would not work. This also means that it can't be used for type checking, really, because the original is vague so it'll accept anything that doesn't work w/ my overloads. My overloads are only really nice for a little bit of type hinting. I'm not sure they're production ready. Also, there might be a non-negligible performance impact of adding a lot of overloads, but I haven't compared with and without.
It's also not that hard to just stick them in your projects if you want them.
@yuudaari Not sure I totally understand. Could you comment with a few examples of what works, and a few of what doesn’t?
In case folks aren't aware, the tuple rest/spread work in #24897 slated for TS 3.0 will probably end up fixing this issue in the not-too-distant future.
@ahejlsberg said:
Strong typing of bind, call, and apply
With this PR it becomes possible to strongly type the
bind
,call
, andapply
methods on function objects. This is however a breaking change for some existing code so we need to investigate the repercussions.
@jcalz to qualify that, #24897:
does:
bind(f3, 42); // (y: string, z: boolean) => void
does not:
bind
to an arbitrary number of arguments without ugly hacks just to get from [A, B]
to [A, B, C]
or the other way around. the good news is apply
and call
should not need this, though bind
, curry
and compose
do.That said, it's a great step forward.
@tycho01 very interesting. Do we know of any plans or PRs that enable us to have a fully strong-typed bind
with an arbitrary amount of parameters?
Also, @ahejlsberg said that "we need to investigate the repercussions". Do we have a timeline on when this will happen, or an issue that keeps track of this progress?
@ffMathy:
repercussions: timeline/issue?
dunno, ask him in there!
Do we know of any plans or PRs that enable us to have a fully strong-typed bind with an arbitrary amount of parameters?
I think the non-hacky tuple Concat
alternative ([...T]
) needed for bind
is what remains of #5453 now after #24897.
Now implemented in #27028.
Most helpful comment
Now implemented in #27028.