Map takes a function and maps the content of a list to another list of the same length. We want to double the numbers in a list, using the function doubler
function doubler(x) {
return x*2;
}
If we call map with this function and the list we get our result:
map(doubler, [1, 2, 3]); // => [2, 4, 6]
This works well as long as the argument of the function doubler
gets the
type of value it expects. If we feed doubler
with a list of strings instead
of numbers, nothing happens! Until we run this function. Then things go horrably wrong.
The type of the list items (string) is not compatible with the type the function argument
doubler
is expecting (number), so we have a collision of types.
We do not need to wait until runtime to discover this type of errors. With TypeScript we get the ability to enforce the correct types during compile time.
To do that we start with `map. Map has the 'signature' (type specification of both input and output):
map:: (a -> b) -> a[] -> b[]
where a
and b
are generic types. It says that map takes a function with
the argument of type a
that returns a value of type b
, and a list of
items that are of type a
. Note that the list item types and the function
argument type are the same. The function returns a value of type b
so it
is logical that the result of map
is a list of values with that same type b
.
Now a
and b
are generic types, which means that can be any type, number,
string, boolean, array, etc. And type a
used as function argument is the
same type a
are the list that is processed by the function. a
and b
may
be of the same type, but that is not a requirement.
In case of the above example of map doubling the values of the list, a
and
b
are both of the type number
, so the signature of map is interpreted as
map:: (number -> number) -> number[] -> number[]
The signature of double is:
doubler:: (number -> number)
The function has no generic signature because the function body (where the number is doubled only works for number, not or string or arrays.
Now, if you use doubler
to double a list of strings it would not match the
signature of map. In javascript you would only notice this are runtime.
Here is where TypeScript comes in handy. TypeScript allows you to add signatures to javascript functions. Your application is a combination of function calls where the output of one function is used as the input of another. When you specify the signatures of all the function, the compiler is able to check if your application is correct in the sense that the function signatures are met. In theory, because...
Using the earlier example in TypeScript you want to make sure that the list items
are of the same type as the function argument. This means you needs to add type
information to map
:
map<A, B>(fn: (a: A) => B, list: A[]) => B[]
You probable see the resemblance with the signature above. One thing might
seem a bit odd: map<A, B>
.
map<A, B>
may be used to let the developer explicitly specify the types
A
and B
used in the signature. This is not always necessary, but we will see
in a moment that we need to use this in our case. So calling map
with the function
doubler
looks like:
map<number, number>(doubler, list)
where we specify that both type A
and B
are number
.
Lets look at three examples. First the mapping without specifying the types.
var res1 = map(double, [1, 2, 3]); // => no compile error, result is correct
The result is as expected, but suppose we accidentely used a list of strings
var res2 = map(double, ['a','b', 'c']); // => no compile error !!!, result is very wrong
This is nasty. The whole point of typing is useless if the compiler does not detect this collision of types! This is the reason you (allows) need to be explicit about the types you expect, so:
var res2 = map<number, number>(double, ['a','b', 'c']); // error
The compiler error says:
example_map.ts(34,40): error TS2345: Argument of type 'string[]' is not assignable to parameter of type 'number[]'.
Type 'string' is not assignable to type 'number'.
The error provides us with a lot of useful information. For the list and column number of the error: (34, 40). Column 40 is where the list starts with its '['. To know the location of the error is very handy when you compose a number of functions that could all cause an error.
The error message tells us that the type string[]
is not assignalble to parameter of type number[]
. When we look
at the signature of map
understand that type variable B[]
is ment, where B is specified to be number
.
The messages also tells us about a second error: type string
can bo be assigned to type number
. It is easy to
see where this message comes from.