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[];

Answers:

Answer

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

Answer

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).

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.