Implementing Clojure Protocols

Published: January 30, 2014


Protocols are Clojure’s way of defining Java interfaces. Here, I will show how to define protocols and how to implement them.

How to Define a Protocol

(defprotocol Drives
  "a protocol for driving"
  (drive [this throttle]))

Now we’ve got a Java interface named Drives with one (polymorphic) public function drive to be implemented by deftype or defrecord. You could also implement the protocol in Java with a little interop magic. I won’t get into this, however.

The drive function is polymorphic in that it will dispatch on the type of the first argument to the function. This is why every protocol function must take at least one argument. You can think of the first argument as always being this.

Implement a Clojure Protocol

Let’s implement the Drives protocol and create a concrete Car class.

(defrecord Car [top-speed]
  Drives
  (drive [_ throttle] (str "Spin wheels " (* top-speed throttle))))

Java equivalent:

class Car implements Drives {
  private float top_speed;

  public Car(float top_speed) {
    this.top_speed = top_speed;
  }

  public String drive(float throttle) {
    return "Spin wheels " + Float.toString(this.top_speed * throttle);
  }
}

Now we’ve got a concrete implementation of Drives named Car. As you can see, we implemented the drive function.

What’s the _ parameter to drive you may ask? It essentially means “I don’t care about this value you’re passing in.” It is convention to use _ as the parameter name for values in which your function doesn’t use. In this case, it would be this, the passed in Car instance.

Let’s drive some cars.

(def ferrari (Car. 200.0))
(def honda (Car. 80.0))

(drive ferrari 0.5) ;;=> Spin wheels 100.0
(drive honda 1.0)   ;;=> Spin wheels 80.0

With this very scientific and accurate physics model, a Ferrari applying only 50% throttle will drive faster than a Honda with the pedal to the floor.

So what’d we do? First, we instantiated two instances of Car by passing in the top-speed value to the constructor. To call one of the functions from the protocol, you do so just as you would any other clojure function. Passing in the object as the first argument.

Java equivalent:

Drives ferrari = new Car(200.0);
Drives honda = new Car(80.0);

ferrari.drive(0.5);
honda.drive(1.0);

Polymorphic Function Dispatch

So we’ve implemented Car and shown that (drive ferrari 0.5) works as you would expect. What about another concrete class Boat which also implements Drives.

(defrecord Boat [top-speed]
  Drives
  (drive [_ throttle] (str "Spin propeller " (* top-speed throttle))))

(def chris-craft (Boat. 70.0))

(drive chris-craft 0.8) ;;=> Spin propeller 56.0

The drive function dispatched to the implementation defined by Boat.

Using Records and Types Elsewhere

Under the covers, the record is essentially a map. This means you can use it just as you would any other map-like data structure.

Get the top-speed of the ferrari.

(:top-speed ferrari) ;;=> 200.0

Create a blue-honda.

(def blue-honda (assoc honda :color "blue"))
;;=> Car{:top-speed 80.0, :color "blue"}

Like this? Buy me a coffee or email me.