1. Introduction
The “duck test” is a colloquial form of reasoning, often used to say that a person can identify an unknown subject simply by observing its peculiar characteristics:
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
That reasoning is not always true, but it’s often used in many contexts, including duck typing.
More precisely, in computer programming and type theory, duck typing is a typing technique where an object is given a type if and only if it defines all the properties needed by that type.
In this tutorial, we’re going to expand on the definition above, seeing what duck typing is with some examples.
2. Definition and Examples
The “duck typing” explanation we saw above makes sense in type theory. However, there are simpler and more intuitive definitions of it. In other words, duck typing means we’re defining an object by what it can do, rather than by what it is. The type of the object doesn’t matter here, nor does its structure: the only thing we’re interested in is what the object can do.
More formally, a duck-typed language will not perform any type checks at either compile time or run time. On the contrary, it will try to invoke the specified methods at run time. If it works, good. Otherwise, it’ll throw some form of run-time error.
Let’s see an example in Python, a duck-typed language:
class Duck:
def swim(self):
print("The duck is swimming")
def fly(self):
print("The duck is flying")
class Pelican:
def swim(self):
print("The pelican is swimming")
def fly(self):
print("The pelican is flying")
class Penguin:
def swim(self):
print("The penguin is swimming")
duck = Duck()
pelican = Pelican()
penguin = Penguin()
duck.swim()
duck.fly()
pelican.swim()
pelican.fly()
penguin.swim()
penguin.fly()
In the example above, we defined three different classes, Duck, Pelican, and Penguin, with methods to fly and swim. Penguins, however, don’t fly. Therefore, our Penguin class won’t implement any fly() method.
After the class definition, we created an instance of each class, calling both swim() and fly() on each. Here’s the output:
The duck is swimming
The duck is flying
The pelican is swimming
The pelican is flying
The penguin is swimming
Traceback (most recent call last):
File "main.py", line 31, in <module>
penguin.fly()
AttributeError: 'Penguin' object has no attribute 'fly'
The output clearly shows duck typing in action. There’s no compiler in Python to tell us beforehand that penguin.fly() is an error. Instead, we get an AttributeError at run time.
2.1. Polymorphism
Simply put, “polymorphism” means “to have multiple forms”. In type theory and computer science, it is a property of programming languages that lets us use one symbol to represent multiple types. In other words, we can use instances of subtypes whenever a supertype is needed.
Let’s see an example in Java:
interface SwimmingAnimal {
public void swim();
}
class Duck implements SwimmingAnimal {
@Override
public void swim() {
System.out.println("The duck is swimming");
}
}
public class Main {
public static void test(SwimmingAnimal sa) {
sa.swim();
}
public static void main(String[] args) {
test(new Duck());
}
}
In the example above, the test() method inputs a parameter of type SwimmingAnimal. Therefore, we can create an instance of Duck (which implements SwimmingAnimal) and provide it as an argument to test().
Duck typing is a form of polymorphism. As we saw above, it lets us use objects of different types interchangeably, provided that they implement certain behaviors (aka their interface). The main difference with other forms of polymorphism (e.g. Java’s) is that the objects don’t have to inherit from a common superclass/interface.
3. Pros and Cons of Duck Typing
As we saw above, in duck-typed languages, we don’t have a compiler tagging expressions with types before running the code. This has several advantages, but it comes at a cost, too:
Pros
Easy prototyping
We can just create snippets of code like the one above, without worrying about defining types or interfaces.
Code reuse
With duck typing, there’s no need to create complex class hierarchies to reuse code.
Flexibility
We can use objects interchangeably based on their behavior, without worrying about labeling them with types.
Cons
Runtime errors
In the Python example above, a compiler would have blocked us from running a snippet of code that would fail at run time.
Readability
The code is less explicit, as we don’t have any types documenting the behavior needed by a particular function.
Debugging and maintenance
Code that’s more difficult to read is also more difficult to debug and reason about.
4. Applicability
Generally speaking, duck typing is very useful for prototyping and scripting. If the application we’re writing is a simple script, not having explicit type definitions in the code can speed up the development process (again, at a cost of an increased risk of run-time errors). The same is true for prototyping: proof-of-concept implementations might skirt the formalization of types to focus on the business logic to solve a particular problem.
Another relevant application of duck typing is serialization/deserialization. For instance, defining types for each JSON object might be cumbersome. Duck typing lets us assume the existence of some properties on the objects we send/receive, simplifying the code.
5. Conclusion
In this article, we dove into duck typing, defining it from different standpoints. Furthermore, we saw code examples demonstrating how it works and its pros and cons. Lastly, we took a look at how duck typing and polymorphism relate.