Blog

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 over Result, 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 a Result and an Element into a new result.
  • Because the return type is Result, the return value of reduce is either initial, or the return value from calling combine.
  • If the array is empty, we don’t have an Element value, so the only thing that can be returned is initial.
  • 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 call combine 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.


Stay up-to-date with our newsletter or follow us on Twitter.

Back to the Blog

recent posts