Spec
According to the MDN specification for Array.prototype.map() map should be used like this...
var new_array = arr.map(callback[, thisArg])
Problem
TypeScript has several overloaded declarations for map, and this makes it very difficult to extend Array<T>
.
I would expect to see this (which is in lib.d.ts)...
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
But lib.d.ts also has these...
map<U>(this: [T, T, T, T, T], callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): [U, U, U, U, U];
map<U>(this: [T, T, T, T], callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): [U, U, U, U];
map<U>(this: [T, T, T], callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): [U, U, U];
map<U>(this: [T, T], callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): [U, U];
Objection
Since JavaScript does not allow method overloading, and neither does TypeScript for class implementation, I don't think that TypeScript should allow this for ambient declarations either.
Questions
- Why does TypeScript allow overloaded signatures for ambient declarations?
- How do I override the map implementation in a class that extends Array?
I've raised this on GitHub too...
Note
ReadonlyArray<T>
only has a single signature for map, which is...
map<U>(callbackfn: (value: T, index: number, array: ReadonlyArray<T>) => U, thisArg?: any): U[];
Spec
According to the MDN specification for Array.prototype.map() map should be used like this...
var new_array = arr.map(callback[, thisArg])
Problem
TypeScript has several overloaded declarations for map, and this makes it very difficult to extend Array<T>
.
I would expect to see this (which is in lib.d.ts)...
map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
But lib.d.ts also has these...
map<U>(this: [T, T, T, T, T], callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): [U, U, U, U, U];
map<U>(this: [T, T, T, T], callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): [U, U, U, U];
map<U>(this: [T, T, T], callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): [U, U, U];
map<U>(this: [T, T], callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): [U, U];
Objection
Since JavaScript does not allow method overloading, and neither does TypeScript for class implementation, I don't think that TypeScript should allow this for ambient declarations either.
Questions
- Why does TypeScript allow overloaded signatures for ambient declarations?
- How do I override the map implementation in a class that extends Array?
I've raised this on GitHub too... https://github./Microsoft/TypeScript/issues/13785
Note
ReadonlyArray<T>
only has a single signature for map, which is...
map<U>(callbackfn: (value: T, index: number, array: ReadonlyArray<T>) => U, thisArg?: any): U[];
Share
Improve this question
edited Jan 31, 2017 at 14:21
Matthew Layton
asked Jan 31, 2017 at 13:50
Matthew LaytonMatthew Layton
42.5k58 gold badges209 silver badges338 bronze badges
2
-
It's interesting that you didn't ask about what IMO would be the most important aspect here: purpose. As in "why define all these overload signatures in the first place?" -- And the answer to that would be the fact, that TypeScript has a type-concept of arrays with a finite amount of elements, such as
[number, number, number]
being a 3-elementnumber[]
. This is assignable both to and fromnumber[]
, but is not precisely the same from the piler's point of view. Also, these are also called tuple-types in some context, and can define not-necessarily inter-assignable elem types. – John Weisz Commented Jan 31, 2017 at 15:00 - @JohnWeisz I get that concept of n-element arrays, but then why does this concept in this respect stop at 5-element arrays? Surely n could be infinite? – Matthew Layton Commented Jan 31, 2017 at 16:08
2 Answers
Reset to default 2(1) If it wouldn't be allowed to overload signatures in ambient declarations how would you get the different signatures in the native js functions/methods?
There are a lot of overloads in the lib.d.ts
which reflects how the native js objects work.
(2) You need to tell the piler that you're covering all of the possible declared signatures.
In your case you can do something like:
class A<T> extends Array<T> {
map<U>(this: Array<U>, ...args: any[]);
map<U>(this: Array<T>, callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[] {
return [];
}
}
The first overload signature takes care of the ones you don't want to bother with.
Your question touches different aspects of TypeScript. I'll handle them individually and then put them all together.
Array<T>
Interfaces serve a dual purpose in TypeScript:
- They allow you to create "true" interfaces for other (not yet existing) classes to implement. When you implement it, you have to implement it entiriely, since the interface provides a guarantee of all its members being available.
- They allow you to define the interface of an already existent javascript type. This is very useful when you want to use one of the many, many existent libraries and still have the advantages of static typing. These interfaces are usually defined in a
.d.ts
file (andlib.d.ts
file contains the basic JavaScript types). These interfaces are tailored to the existing type and they are usually not meant for you to implement. You can though if you want, but you have to implement all its members.
The Array<T>
interface is of the second kind and as such it is not meant for you to implement.
this
Parameters in Functions
The this:
parameter in the function definition is not a real parameter in the sense that you can pass arguments. It allows you specify which type you expect the this
value in the function body to be. If you don't specify it, this
will be of type any
, which is often not very useful.
Function/Method Overloading
In TypeScript, functions and methods are not overloaded in the sense they are overloaded in languages like Java or C#. You cannot implement it more often than once, but you can define alternate signatures to allow static typing for functions that return variant types or use variant parameters. Especially in .d.ts
definition files this is useful and often necessary, since existing libraries use the weak typing of JavaScript to return values or expect parameters of different types. That makes your objection false. You need function overloading to acmodate these JavaScript constructs.
Tuples
In a JavaScript array you can assign values of multiple types in each of its slots. In a TypeScript array definition you specify one type, which the plier enforces. To fill the gap, you can define tuples. A type like [number, string, string]
translates to a JavaScript array any[]
and TypeScript can still enforce static typing.
Summary
The array method overloads you object against in Array<T>
introduce a statically typed this
parameter in the event the array is an actual [T, T]
, [T, T, T]
, [T, T, T, T]
, [T, T, T, T, T]
. It does not imply that the array provides multiple map
methods. It is the same method, but overloaded for some specific array types. It is provided in lib.d.ts
and as such was not designed to be implemented by you. You can extend the underlying class (which is already there even without the interface), but the overloads don't hurt you (at least not in this case, since they only provide this
parameters).