Swift Tip: Generics vs. Any
We are busy updating our book, Advanced Swift — the new edition will be out by the end of April, as a free upgrade if you previously bought the Ebook.
As part of the update, we are rewriting the chapter on generics from scratch. In this post we preview an excerpt from our most recent draft.
Generics and Any
are often used for similar purposes, yet they behave very differently. In languages without generics, you typically use a combination of Any
and runtime programming, whereas generics are statically checked at compile time.
When reading code, generics can help to understand what a method or function is doing. As an illustration, consider the type of reduce
on arrays:
extension Array {
func reduce<Result>(_ initial: Result, _ combine: (Result, Element) -> Result) -> Result
}
Assuming that reduce
has a sensible implementation, you can already tell a lot from the type signature without looking at the implementation:
-
We know that
reduce
is generic overResult
, which is also the return type. -
Looking at the input parameters, we can see that the function wants some value of
Result
, and a way to combine aResult
and anElement
into a new result. -
Because the return type is
Result
, the return value ofreduce
is eitherinitial
, or the return value from callingcombine
. -
If the array is empty, we don't have an
Element
value, so the only thing that can be returned isinitial
. -
If the array is not empty, the type leaves some implementation freedom: the method could return
initial
without looking at the elements, the method could callcombine
with one of the elements (e.g. the first or the last element), or with all of the elements.
There are, of course, an infinite number of other possible implementations. For example, the implementation could call combine
only for some of the elements. It could use runtime type introspection, mutate some global state, or make a network call. However, none of these would qualify as a sensible implementation. In fact, because reduce
is defined in the standard library, and we assume the standard library authors are sensible people, we can be certain that it has a sensible implementation.
Now consider the same method, defined using Any
:
extension Array {
func reduce(Any, (Any, Any) -> Any) -> Any
}
This type has much less information, even if we only consider sensible implementations. By looking at just the type, we can't really tell the relation between the first parameter and the return value. Likewise, it's unclear in which order the arguments are passed to the combining function. It's not even clear that the combining function is used to combine a result and an element.
In our experience, generic types are a great help when reading code. Specifically, when we see a very generic function or method such as reduce
or map
, we don't have to guess what it does: the number of possible sensible implementations are limited by the type.
For more theoretical background reading, check out Philip Wadler's paper, Theorems for Free.
To start reading Advanced Swift, you can purchase it here.