This post is about building data abstractions in Clojure. The objective is to define abstract interfaces using only functions. We will not resort to mechanisms like protocols.

The technique we describe here is used in languages like Scheme, where functions are the only means of abstraction available.

As an example, consider the stack data type. The basic interface for a stack consists of two functions - push and pop. We can also add a third function for inspecting the element at the top of the stack.

The implementation of the interface assumes that an object providing the services of a stack should respond to the messages - push, pop and top. As we are strictly limiting our implementation to use only functions, the objects themselves will be represented as functions that take these messages as keyword arguments. This leads to the following implementation of the stack interface:

(defn push [stack item]
  ((stack :push) item))

(defn pop [stack]
  ((stack :pop)))

(defn top [stack]
  ((stack :top)))

The actual implementation of a stack “object” that conforms to the above interface is shown next:

(defn make-stack
  ([items]
   (let [push (fn [item]
                (make-stack (cons item items)))
         pop (fn []
               (make-stack (rest items)))
         top (fn []
               (first items))]
     (fn [message]
       (case message
         :push push
         :pop pop
         :top top))))
  ([] (make-stack [])))

The make-stack function will return a function that can respond to the required messages. The stack itself is internally represented as an immutable sequence.

The following session at the REPL shows that the immutable stack implementation can be used with the abstract interface that we defined earlier:

> (def s (-> (make-stack) (push 10) (push 20) (push 30)))
> (top s)
;;=> 30
> (top (-> s (pop) (pop)))
;;=> 10

Here are a few ideas you can use to play around with our simple interface definition technique:

Exercise 1. Implement a mutable stack that can be used with the interface functions. Note that to be a stand-in replacement for the immutable stack, the implementation of push and pop must return the current stack “object” (i.e, this in Java).

Exercise 2. Extend the interface with a function that reports the number of elements in a stack.

Exercise 3. Add a visit function to the interface. It should take a single-parameter function that is applied to each element in the stack in LIFO order and return a sequence of results.


Note that name and e-mail are required for posting comments