This essay will look at how we can understand programming from a mathematical point of view. I'll be using the Swift language as an example, but the general concepts should apply to most programming languages. I will be covering some material which you may already be familiar with, such as sum types and product types. I will also be discussing other topics such as the peculiar properties of types with one or zero values. I'll wrap it up by showing how an interesting algebraic structure called a semiring fits into the picture.
Swift is composed of data types such as String, Int, Bool, Array, etc. We can think of a type as a set of values. An instance of a type represents one of these values. Bool has two values, true and false. Int on a 32-bit platform has over 4 billion values. String and Array theoretically have an infinite amount of values, but in reality they are constrained by memory and implementation details.
The number of values in a set is called the set's cardinality. When I refer to the size of a type, I will be referring to the cardinality of its set of values.
Swift supports different kinds of types. Some basic kinds are structures, classes, enumerations, tuples and functions. A few types such as Int and Character are simple – they can’t be broken down into component types. Structures and classes are composed of properties which are themselves separate types. A tuple is an ordered set of two or more types. A tuple may also be empty, but it cannot contain a single type. A tuple with a single type would be redundant since it would be equivalent to the type it contains.
Structures, classes and tuples serve different purposes within a programming environment, but they are equivalent from a mathematical point of view. Each instance of these types contains an instance of each of its properties. I'll be discussing structures and tuples for the most part, but keep in mind that the mathematical concepts are the same for all three types.
Collections, such as Array and String, can hold a variable number of instances of a certain type. This is the reason why their size is theoretically infinite. Theoretically, we can always add more Characters to a String.
Now let's look more carefully at the idea that a type is a set of values. Simple types such as Int and Character have a predefined number of values. For example, Int32 is the set of integers from -2,147,483,648 to 2,147,483,647.
It is a little harder to determine what the set of values for Character is, but we can at least show some of them,
Bool is an odd type. It is a structure but it has no properties and it only has two values.
In some ways it is more like an enumeration, but we can’t switch over it.
Sum and Product Types
An enumeration contains a number of different cases and each case represents a value. The number of values for an enumeration corresponds to the number of its cases. This isn't exactly true if we consider associated types, but we'll get to that later.
Enumerations are called sum types because we can calculate their number of values by summing the number of their cases.
When we have a composite type we have to consider the number of values in each property. But to determine the size of a composite type, we can't just add the number of values in each property. For example, let's say we have a Color enumeration with three cases and a structure which includes this enumeration and a Bool.
enum Color {
  case blue
  case red
  case yellow
}
struct Style {
  let color: Color
  let available: Bool
}If we add the values in Color and Bool we get 5. But Style actually has six possible values.
A value of a composite type is a combination of values, one for each of its properties. So the size of a composite type is the number of possible combinations of its property values.
Composite types are called product types because we calculate their number of values by multiplying the number of values in their component types.
Let's come back to associated types in enumerations. Without associated types, each case in an enumeration is a single value. If we add an associated type we have to take the size of this type into account. Suppose we have the following enumeration,
enum PartyDecoration {
  case balloon(Color)
  case confetti
  case streamer
}confetti and streamer are ordinary cases. Each is a single value. But the balloon case has an associated Color type. Color is also an enumeration. It has three simple cases, so it has three values. To get the total number of values for PartyDecoration we need to include the number of values in the enumerated cases including the sizes of associated types. So PartyDecoration has five values, 1 + 1 + 3 = 5. Its set of possible values looks like this.
Exponential Types
Functions seem out of place in this discussion, but they are also types. A function is a type with two parts, a set of parameters and a return type. To simplify things, let's pretend the set of parameters is a single type. If there is more than one parameter, we can think of them as a single tuple parameter.
Since we're talking about types as sets of values, it would be helpful to go over the mathematical definition of a function. A function in mathematics is a mapping between one set and another. The first set is called the domain and the second set is the codomain of the function. A function maps every element in the domain to some element in the codomain. Every element in the domain must get mapped, but a function may not necessarily cover every element in the codomain.
For example, we could have a function which takes a Color value and returns a Bool.
func isBlue(_ color: Color) -> Bool {
  switch color {
  case .blue: return true
  case .red: return false
  case .yellow: return false
  }
}The domain is Color and the codomain is Bool. This function maps each element of Color to an element in Bool. For blue it returns true and it returns false for all other colors. This isn't the only function that goes from Color to Bool. We could also have the following.
func isNotRed(_ color: Color) -> Bool {
  switch color {
  case .blue: return true
  case .red: return false
  case .yellow: return true
  }
}We can now ask, how many possible functions are there that go from Color to Bool? How many mappings are there from the set of values in Color to the set of values in Bool? We can show each mapping as a set of tuples where the first element of the tuple is a Color value and the second element is a Bool value.
We have eight possible mappings. There are eight functions which go from Color to Bool. We showed each function as a set of tuples. The first element of the tuple is the function argument and the second element is the result. The first elements are always going to be the same for each function since we have to cover all the values in the domain. We can remove the domain elements to make it easier to read.
We can now see that what we were looking for were all the possible permutations of three Bool values. Since Bool has two possible values, the answer is 2 x 2 x 2 = 8, or 2^3. The "three" comes from the size of the parameter type. So more generally, if a parameter type A has m values and a return type B has n values, there are n^m functions that go from A to B.
Function types are called exponential types because we can calculate their number of values by raising the size of the return type to the power of the size of the parameter type. Notice that the parameter size is the exponent. This can be confusing since parameters come first in the definition of a function.
Note the difference between functions and function types. A function is a single mapping from a domain to a codomain. A function type, considered as a set of values, consists of all the possible functions that map from the domain to the codomain. Also note that function names are irrelevant. There may be many functions which do the same mapping. For the purposes of this discussion we can consider them all as the same function.


