Clojure defprotocol as a solution to the expression problem
clojure, polymorphism
Solution
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.
Problem
In the book "The Joy of Clojure", `defprotocol` is offered as a solution to the expression problem -- "the desire to implement an existing set of abstract methods for an existing concrete class without having to change the code that defines either." The example given is as follows: ``` (defprotocol Concatenatable (cat [this other])) (extend-type String Concatenatable (cat [this other] (.concat this other))) (cat "House" " of Leaves") ;=> "House of Leaves" (extend-type java.util.List Concatenatable (cat [this other] (concat this other))) (cat [1 2 3] [4 5 6]) ;=> (1 2 3 4 5 6) ``` It is suggested that this is not possible in a language like Java, but how is it different than the following? ``` public class Util { public static String cat(final String first, final String second) { return first.concat(second); } public static <T> List<T> cat(final List<T> first, final List<T> second) { final List<T> list = new List<T>(first); list.addAll(second); return list; } } ``` After all, both are used similarly: ``` (cat "House" " of Leaves") Util.cat("House", " of Leaves"); ``` The Clojure function `cat` is not a method on the `String` and `List` classes, but rather an independent function that is overloaded to accept either `String` or `List` arguments. Although I really like Clojure, I don't understand the claims of superiority for this construct.