Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript Array.prototype.map declaration

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

  1. Why does TypeScript allow overloaded signatures for ambient declarations?
  2. How do I override the map implementation in a class that extends Array?

I've raised this on GitHub too... https://github.com/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[];
like image 237
Matthew Layton Avatar asked Jan 31 '17 13:01

Matthew Layton


People also ask

What is array prototype map () useful for?

Array.prototype.map() The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.

Does map return a new array?

The map() method returns an entirely new array with transformed elements and the same amount of data. In the case of forEach() , even if it returns undefined , it will mutate the original array with the callback .

What is map () in JavaScript?

Definition and Usage. map() creates a new array from calling a function for every array element. map() calls a function once for each element in an array. map() does not execute the function for empty elements. map() does not change the original array.

How does an array map work?

The map() method in JavaScript creates an array by calling a specific function on each element present in the parent array. It is a non-mutating method. Generally map() method is used to iterate over an array and calling function on every element of array.


2 Answers

(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 compiler 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.

like image 164
Nitzan Tomer Avatar answered Oct 19 '22 02:10

Nitzan Tomer


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:

  1. 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.
  2. 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 (and lib.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 accommodate 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 complier 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).

like image 1
Sefe Avatar answered Oct 19 '22 00:10

Sefe