Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure defprotocol as a solution to the expression 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.

like image 341
Ralph Avatar asked May 13 '11 00:05

Ralph


1 Answers

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 cats. 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.

like image 134
amalloy Avatar answered Sep 19 '22 21:09

amalloy