Clojure defprotocol as a solution to the expression problem
Okay. You release this cat
Java library to much fanfare, and everyone downloads it. It's so great I want to make my own TVCommercial
type be concatenable so that I can send it to bits of your library that operate on concatenable objects.
But I can't, because you call Util.cat(obj1, obj2)
, which has no overload for TVCommercial
. I can't extend your code to handle my types, because I don't own your code.
You can define Concatenable as an interface to address this problem:
interface Concatenable {
Concatenable cat(Concatenable other);
}
But now I can't write a class which is both Concatenable and... I don't know, an AnimalHandler, that handles cat
s. Clojure's protocols solve both problems by decentralizing the dispatch functions and implementations: they live all over the place, rather than in some single location. In Java, you choose between:
- Putting all your type dispatch into a single switch/case or overloaded method
- Defining an interface mandating a method with a particular name
Clojure basically does the latter of these, but because it uses namespaced names, there's no danger of conflict with other protocols that think cat
is a good function name.
Each time a new type comes along to which you'd like to apply your cat
function, you need to "reopen" your Util
class and add method overloads for the new target types.
The Expression Problem seeks to avoid this need, such that existing types aren't disturbed by new operations being defined and existing implemented operations aren't disturbed by new types that want to participate in those operations. The Clojure protocol example shown here doesn't fulfill the first goal, as adding a new function to a published protocol requires that all types to which the protocol is already extended define an implementation for that new method.