Overwhelmed By Types

Dear Computer

Chapter 12: Polymorphism Revisited

Overwhelmed By Types

What are programmers most afraid of? Repeating themselves. They tirelessly endeavor to never write the same code twice. They identify a recurring pattern of execution and then loop over the pattern. They parameterize an algorithm and express it as a reusable function. They factor out common state and behaviors into a superclass so that subclasses can inherit it.

Languages with dynamic typing—like Ruby, Python, and JavaScript—tend to enable more reusable code than statically-typed languages. For example, this Ruby function will turn any list into an HTML ul tag:

Ruby
def listToHtml(items)
  bullets = items.map { |item| "<li>#{item}</li>" }
  "<ul>#{bullets.join}</ul>"
end

The list could hold numbers, strings, bands, symptoms, ultimatums, enemies of the state, or anything really, as long the objects implement to_s. The function works with all manner of objects because types play a diminished role in dynamically-typed languages. The operations a value supports matter more than its type.

Languages with static typing—like C and Java—don't have it so easy. When we write a function in a statically-typed language, we have to commit to serving a particular type. This narrowing of focus certainly has advantages: the compiler has enough information to typecheck our code, and the executable will run faster because the compiler will generate machine code specific to the type instead of asking questions about the data at runtime. But the glimmer of these advantages starts to fade as our codebase acquires lots of similar but not identical types. Are we going to need to write one version of listToHtml for integers, another for floats, yet another for strings, and still another for each new type we define?

Good news! We will not have to repeat ourselves, because polymorphism has our back. Statically-typed languages grant programmers these two different polymorphic superpowers with which we write a single chunk of code that serves many types:

We have touched upon both of these polymorphisms in earlier chapters. Through the lens of C++, we examined how subclass methods are called on a supertype reference if the methods are virtual. Through the lens of Haskell, we examined how functions and other types can receive type parameters and how type constraints may be placed on type parameters through typeclasses.

There's also ad hoc polymorphism—or overloading—in which we write a version of a function for each type that we wish to that function to serve. Overloading means that we do repeat ourselves, but at least it gives us a common interface.

In this chapter, we revisit these two static polymorphisms. By its end, you'll be able to answer the following questions:

At the end of the day, a programmer using a statically-typed language will not have to repeat themselves any more than a programmer using a dynamically-typed language. However, both polymorphisms must be employed with care to ensure the safety and speed that programmers expect in a statically-typed language.

We begin our discussion by examining parametric polymorphism in Java, C++, and Rust.

Parametric Polymorphism in Java →