Skip to content

Instantly share code, notes, and snippets.

@donnut
Last active August 29, 2015 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save donnut/37b705a9f6360548a236 to your computer and use it in GitHub Desktop.
Save donnut/37b705a9f6360548a236 to your computer and use it in GitHub Desktop.
TypeScript and mapping

Typescript and mapping

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.

Signatures

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

Types in action

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment