Stian Eikeland bio photo

Stian Eikeland

Developer. Does consultancy work from own company. Lives in Bergen, Norway.

Banana Phones, Clojure and the Expression Problem

Clojure is a Functional First language, and I usually get by simply by passing around native datastructures such as maps, lists and vectors. Other than doing some simple Java interop I haven't really dug deep into the Object-Oriented features of Clojure until now. But, just because a language is FF doesn't mean that it's OO is weak sauce.

The Banana

(ns oop.fruits)

(defprotocol Fruit
  (peel [this])
  (describe-fruit [this]))

(defrecord Banana [degree peeled?]
  Fruit
  (peel [this] (Banana. degree true))
  (describe-fruit [this]
    (str "A "
         (if peeled? "peeled" "unpeeled")
         " banana with a " degree " degrees bend")))

(defn get-a-nice-banana []
  (Banana. 30 false))

Imagine that we have a namespace called fruits, in this ns we have a Protocol defined - called Fruit - anything that satisfied being a fruit needs two methods - a function to peel the fruit, and a function to describe the fruit. You can think of Protocols as being something pretty close to "interfaces" in OO languages such as Java and C#. (This whole setup works pretty much as traits).

We also describe a record, a Banana. Think of this as a class, the banana accepts two values - a degree saying how bent the banana is, and a bool saying if it's peeled or not.

Records are often method-less, this one however has two methods peel and describe-fruit, this makes the Banana satisfy the requirements for being a Fruit.

We also define a public get-a-nice-banana function that returns an instance of Banana - a unpeeled 30 degree bend banana - since bananas with 30 degree bends are the best - as we all know.

The Apple iPhone

(ns oop.phone)

(defprotocol Phone
  (call [this number]))

(defrecord iPhone [model]
  Phone
  (call [this number]
    (str "Calling " number " using apple phone model " model "..")))

(defn call-using [phone number]
  (call phone number))

(defn get-a-nice-phone []
  (iPhone. "5s"))

Next, let's define a new namespace. This time we create a Phone protocol, phones should be able to call numbers.

Then we create an iPhone, which fulfills the requirements of being a phone since it can actually call people (not just toot and instaface and..).

We create two functions, call-using which allows you to call a number using a given phone. And a convenience function for getting a nice shiny new iPhone 5s.

We now have a couple of things defined.. We have Phones and Fruits, iPhones and Bananas. They have absolutely nothing to do with each other, and are isolated in their own namespaces.

Apples and Bananas - to the core

(ns oop.core
  (:require [oop.fruits :as fruit]
            [oop.phone :as phone])
  (:import [oop.fruits Banana]))

Let's start a new namespace, oop.core, this namespace imports the two other namespaces we've defined.

(def iphone (phone/get-a-nice-phone))

(phone/call-using iphone 80020123)
;; => "Calling 80020123 using apple phone model 5s.."

(type iphone)
;; => oop.phone.iPhone

(satisfies? phone/Phone iphone)
;; => true

We can use the convenience function from phone to get a shiny iPhone, we see that we can give this phone to call-using which then can use it to call someone. No surprises here, as the iphone satisfies the Phone protocol.

(def banana-phone (fruit/get-a-nice-banana))

(type banana-phone)
;; => oop.fruits.Banana

(satisfies? fruit/Fruit banana-phone)
;; => true

(fruit/describe-fruit banana-phone)
;; => "A unpeeled banana with a 30 degrees bend"

(fruit/describe-fruit (fruit/peel banana-phone))
;; => "A peeled banana with a 30 degrees bend"

Let's try to create a banana, it's a nice banana of the type oop.fruits.Banana, it satisfies the Fruit protocol from the oop.fruits namespace. We can call describe-fruit on it, and we can peel it. No surprises here either.

Now, what if we could actually take our banana and call someone? Wouldn't that be wonderful? But alas..

(phone/call-using banana-phone 80020123)
;; Booom exception.. no implementation of oop.phone/Phone for class oop.fruits.Banana

(satisfies? phone/Phone banana-phone)
;; => false

The banana blows up, spewing yellow goo all over our system - it turns out that a Banana isn't actually a Phone. That makes me a very sad panda :( I've always wanted a real banana phone.

In a regular OO language like Java or C#, I would probably attack this by encapsulating a banana instance in a new class, one that also satisfies the Phone interface.

But no worries, watch this:

(extend-type Banana
  phone/Phone
  (call [this number]
    (str "Calling " number " using a " (:degree this) " degrees bent banana.. WTF?")))

Still in the oop.core namespace, we extend the oop.fruits/Banana type to also satisfy the oop.phone/Phone protocol.

(type (fruit/get-a-nice-banana))
;; => oop.fruits.Banana

Our banana is still just of type Banana.

(satisfies? phone/Phone (fruit/get-a-nice-banana))
;; => true

A Banana is a Phone, it actually satisfies the Phone Protocol... and..

(phone/call-using (fruit/get-a-nice-banana) 80020123)
;; => "Calling 80020123 using a 30 degrees bent banana.. WTF?"

Holy cow! We can now not only call using the Apple iPhone 5s, but also using the 30 degrees bent Banana! And that's using a fresh banana we just got from the oop.fruits namespace - we didn't monkey patch it or modify it. We didn't change the oop.fruits namespace or the oop.phone namespace, we just passed it along to oop.phone.

Mind blow, that's a really elegant solution to the expression problem if you ask me..

Ring ring ring ring ring ring ring bananaphone.

Ping pong ping pong ping pong ping pananaphone.

Bonus feature

As a bonus, here's another pretty nice feature called reify. Reification means to make something real, bring it into being or make something concrete.

In clojure using reify you can actually implement an interface (protocol) anonymously. Observe:

(def anon-phone (reify phone/Phone
                  (call [this number]
                    (str "Calling " number " anonymously.."))))

(phone/call-using anon-phone 80020123)
;; => "Calling 80020123 anonymously.."

An anonymous implementation of the Phone protocol. Try doing that in C# (Håvard tells me you can do it in F#). I thought this wasn't possible in Java, but apparently my limited J-knowledge had me fooled (Thanks Magnus) - so this bonus feature isn't as impressive as I thought :) (reify can do multiple interfaces in one go though, but I guess that's not something I'll use very often)