Function types
A function type is a set of morphisms between two objects in a category . This set is called the hom-set .
Function types are special because they live in the same category as the objects they map which for our use case is or .
A function type has an input type and a return type, so it is a composite type. Here we attempt to use a universal construction to create a function type which is a pattern that involves three objects; the function type itself, the argument type and the return type.
This pattern is function application. We simply apply the function to the argument in order to get the return type. To begin this universal construction we take some for the function type, some for the argument type and some for the return type. These are all objects because we are expressing this construction in terms of arbitrary categories.
The function application itself is a mapping from the product of the function type and the argument type (which is an object in some category) to some other object in the category. In full where is the morphism.
If we think in terms of the category (or more specifically for our purposes, ) then the object is the set of all function types, while the objects and are, respectively, the sets of argument and return types. Note that for and we have some .
This pattern doesn't exist in every category, but it does exist in the categories of interest to programmers. Also, this only exists in those categories which have products for all pairs of objects.
As we saw in the universal constructions of product and coproduct we need to find the best candidate for the function type. That means there is a unique mapping between these candidates and our pattern (which we take to be the best candidate). In this case we take to be the best candidate if for there is a unique morphism such that the application of factorizes through the application of .
We really need to close the diagram through a morphism from to which is possible because the product is a bifunctor and so we can use the morphism which leads to the factorization .
Therefore our is the best candidate, and so we do some renaming as in the diagram below:
Which gives us the formal definition of the universal function object that together with the morphism factors some through the unique morphism .
This function object doesn't exist for any pair of objects in any given category but it does always exist in the category , and therefore also in .
This is really currying
Think of the morphism as a function in two arguments. In fact, in (and ), this is exactly the function from a pair of objects (which themselves are sets - types).
Additionally the universal property tells us that for every there is a unique morphism .
In the categories of interest to us as programmers, and given the above (in those categories) we know that is a function of one argument that returns a function . It is a higher order function. This shows the equivalence of and which is called currying where is the curried version of . Conversely, given the unique we can always construct which is the uncurried version of h:
Function types are exponentials
What might seem bizarre at first actually makes a lot of sense. Given a function we can define it's exponential as . A function from has some set of values (possibly infinite in Haskell), and therefore for each of those we can multiply by the set of values in . Remember we are dealing with objects that are actually sets as we are in our usual categories of interest.
An object from is defined by a pair and ). Let's say we map this to a hypothetical which has 256 possible values (in Haskell the type actually encodes Unicode). That means for every value of the argument type there are 256 possible values in the return type. Therefore the total is .
The laws of exponents map directly to types.