Maps
Introduction
Since I haven't said what "space" means, this is kind of meaningless. Once we encounter precise definitions of various types of spaces, they'll be accompanied by precise notions of "map". Still, I'm going to use the word "map" more than "function" because it's shorter and more evocative. Also, sets are one kind of space (with no additional structure), and in that case map does mean any function.
Terminology
The difference between codomain and range can be hard to appreciate at first. The domain and codomain are part of the "type" of wheras the image might be something difficult to determine.
In terms of vibes, the notation is approximately equivalent to
f: (x: X) => Y;
A more accurate (though still imperfect) translation would be
f: Map<X, Y>;
This is because in math, two functions and are considered equal if
- they have the same domain and codomain
- they map each input to the same output: for all
The second point is an important difference between functions in math and functions in programming (which are really algorithms). For mathematical functions, there's no such thing as a "function body" or "runtime".
Some more terminology.
Exponential interpretation
When we introduced products of spaces, we saw that that concept was related to multiplication of numbers. Functions are also related to a numeric concept: exponentiation.
For example, say we have two balls which can be painted any of three colors, red, green, blue. We can represent the possibilities as functions from to e.g.
There are possibilities in total:
In general, if and are finite sets with and elements respectively, then the number of functions from to is there are options where to send each and we can choose those values independently of one another.
This suggests defining as the set of functions from to so that
This is called the function set or mapping space from to Although this can be hard to think about, it's very powerful to go from thinking about
- individual elements of and to
- individual functions to
- considering all functions as a single object.
Another notation for the mapping space (more common in CS than in math) is in which case the notation is literally a type declaration. In any case, the mapping space from to is approximately equivalent to the type (x: X) => Y
or Map<X, Y>
.
I'll try to avoid using the concept of mapping space except when we have precise definitions of "space" and "map", since
-
the precise definition of "map" will affect what the actual elements of are. For example,
is considered an affine map but not a linear map.
- depending on the precise meaning of "space", it may be easy, difficult, or impossible to turn the set of maps into a "space".
Maps and products
Let's see how this interacts with the other way we've seen of combining spaces, namely products.
Repeated multiplication
When is a natural number ( and is any number, the exponential is defined as repreated multiplication:
Something similar is true for function spaces. Recall that three-dimensional space can be understood as the threefold product of with itself,
We can also view as the function space In code, this is saying that the types
[number, number, number];
and
{
0: number;
1: number;
2: number;
}
are "equivalent".
Depending on your application, you might prefer to have types like
{
x: number;
y: number;
z: number;
}
or
{
height: number;
width: number;
}
These can be encoded as the mapping spaces and
Maps into a product
Let and be spaces. We can form the product and then ask what maps look like.
By definition, needs to send each element of to an element of An element of is a pair with and So can define maps and as the first and second components of
In other words, a function from to is equivalent to two separate functions and . In terms of mapping spaces, we can express this as
(I'll say more about in a future post.) This parallels the identity for numbers
Here's what this equivalence looks like in code:
/** Projection onto first factor */
const pr1 = <X, Y>([x, y]: [X, Y]): X => x;
/** Projection onto second factor */
const pr2 = <X, Y>([x, y]: [X, Y]): Y => y;
/** Convert a function into a product to a pair of functions **/
const split = <X, Y, Z>(f: (x: X) => [Y, Z]): [(x: X) => Y, (x: X) => Z] => [
(x) => pr1(f(x)),
(x) => pr2(f(x)),
];
/** Convert a pair of functions to a function into a product */
const combine =
<X, Y, Z>(g: (x: X) => Y, h: (x: X) => Z): ((x: X) => [Y, Z]) =>
(x) =>
[g(x), h(x)];
Maps out of a product
What about maps out of a product? That is, for spaces let's think about what maps look like.
These correspond to "functions of two variables": for example, the function
is a map
There is another way of encoding a function of two variables: for a fixed value of we can get a function by defining
For instance, if as above, then This is saying that a function of two variables can also be expressed as a function from to the set of functions from to . Symbolically, this is
or
This "lifts" the numeric identity
In programming, this equivalence is called "currying".
/** Convert a function of two variables into a function returning a function. */
const curry =
<X, Y, Z>(f: ([x, y]: [X, Y]) => Z): ((x: X) => (y: Y) => Z) =>
(x) =>
(y) =>
f([x, y]);
/** Convert a function returning a function to a function of two variables. */
const uncurry =
<X, Y, Z>(f: (x: X) => (y: Y) => Z): (([x, y]: [X, Y]) => Z) =>
([x, y]) =>
f(x)(y);
Coproducts
There's one more exponential identity remaining,
To get an analogue of this for mapping spaces, we need the concept of the coproduct I don't want to really get into this concept right now, but it corresponds to the union type X | Y
. The relevant identity is
Or if you prefer, this is saying that the types Map<X | Y, Z>
and [Map<X, Z>, Map<Y, Z>]
are "equivalent".
Convince yourself of the above, and implement the conversion in code.