The Duck is a Lie
Join the DZone community and get the full member experience.
Join For FreeWhat follows is my experience with Java, PHP and Ruby. I mainly use PHP as a dynamic language that supports duck typing but also the definition of Java-like interfaces, but does not force any of the two approaches as you can define interfaces whose method arguments accept any variable or not using interfaces at all.
Is duck typing that a revolution?
Misconceptions I've seen
First of all, duck typing is not a means for making objects work together even when designed by different people or projects. For example, given a send() method it could be implemented in so many versions (like send(), sendX(), send(x, y)) that it is highly unlikely that without defining a common interface the objects will call each other correctly. Even when the signature seems similar, the objects passed as the first or second or n-th argument could be actually different.
Moreover, it is possible to encounter different implementations of send() which actually weren't meant to conform to the same interface: think of sending an e-mail and sending a Facebook notification. This clash of interfaces would be detected by a type system or at runtime in hybrid languages, but results in strange errors (like undefined methods error when a message is sent to these arguments.)
Duck typing works well when the implicit interfaces are well-known: for example, when overriding the + operator, or the equals() and compareTo() methods.
Explicit interfaces
That's not to say that static checks and explicit interfaces are enough to prevent all errors, as you can always implement an interface in an inconsistent way and (without design-by-contract) there is nothing that will save you:
interface SortingAlgorithm { public function sort(array $objects); } class BubbleSort implements SortingAlgorithm { ... } class Quicksort implements SortingAlgorithm { ... } class MergeSort implements SortingAlgorithm { ... }
What's the difference between these two classes?
- Quicksort in efficient implementations is not a stable sort (an algorithm that leaves objects with equal keys in the same order as in the original collection). Mergesort instead is always a stable sort.
- Are the algorithms sorting in ascending or descending order?
- And on which key?
Actually, we could argue that these are the kind of information hiding that we want, so that we are able to change our sorting mechanism after a user request or a new functional requirement just by swapping in an object. But Java and PHP interfaces are not contracts: the only specify typing requirements but no preconditions or postconditions.
With duck typing, the picture would be (keeping PHP as the language for consistency):
class BubbleSort { public function sort(array $object) { ... } } class QuickSort { public function sort(array $object) { ... } } class MergeSort { public function sort(array $object) { ... } }
But in this case we don't know anymore that there was the intent of the original developer was to make these algorithms/objects compatible, or what requirements we should follow to implement a new one. Some of these problems can be alleviated with the acts-as pattern, but I believe not all of them.
The acts-as pattern
In Ruby, one of the most popular duck-typed languages, it is common to implement duck typing with an external module (to be mixed in in implementing classes) that accepts calls to a primitive named acts_as_something.
For example, this definition would add several methods to the class like first?, insert_at and so on:
class Product < ActiveRecord::Base acts_as_list end
You can actually pass methods and procs (anonymous functions) to an acts-as primitive, effectively providing an implementation where the external interface is always consistent (because it consists in methods defined inside the module.)
However, in these cases pattern is the realization of a Template Method mechanism (or a Bridge pattern at best) where the internal interface between the object and the module suffers the same fate as the original sort() function:
- what I should pass to acts_as_something?
- When one of the arguments is a proc, how many arguments it accepts and which messages they accept in turn?
Moreover, if you had to implement every implicit interface with Template Methods you'll quickly grow your classes. Ruby "fixes" the problem by allowing multiple inheritance (that's what modules really are), so that you can extend as many classes as you need to provide as many Template Methods as you need.
The single callback fallacy
What I am starting to hate are PHP methods that think that all closures are equal. Their duck-typed signature typically is:
public function doSomething(callable $callback) { ... }
where $callback is a closure or an object defining __invoke(); something that you can call with $callback(/* arguments in here */).
The problem? I have to ask many questions when I see this signature:
- how many arguments does $callback take? Does it have any?
- Which methods each of this argument exposes? That is, I can call $firstArgument->x() or $secondArgument->y()?
- If I'm consciously violating the Law/Suggestion of Demeter, and calling methods on the result of $firstArgument->x(), what can I call?
- What should $callback return, if anything?
That's why I now prefer in more and more cases to define explicit interfaces even where I have a single method:
interface ThatCallback { public function __invoke(AClass $firstArgument, BClass $secondArgument); }
The problem with this approach in PHP is that you can't define a private inner class implementing this, so you'll need a new source file:
class MyCallback implements ThatCallback { public function __invoke(AClass $firstArgument, BClass $secondArgument) { ... } }
Java does it better by allowing you to even create anonymous classes inside your methods:
new ThatCallback() { public void invoke(AClass firstArgument, BClass secondArgument) { ... } }
Duck typing is the absence of types: to provide documentation for an interface means writing down which messages it accepts, and in turn what the arguments and the return value of the implementing methods accept. With an explicit interface, you just refer to the type AClass and BClass from any place in your code, breaking the recursive specification of messages at the first level. The only alternative is not to document.
Conclusions
Explicit interfaces are centers that collect in a single place all the information you want to provide about a contract, avoiding the duplication of the list of accepted messages of an object in all the locations where it is cited as a collaborator, an argument to a method or a return value.
I don't want to be force to define an interface, but to have the ability to do so in the time of need. Static checks like interfaces only catch a certain category of errors, but their utility is not limited to constraining the programmer but also to the reification of a concept.
Opinions expressed by DZone contributors are their own.
Comments