Skip to content
Bill La Forge edited this page Nov 23, 2015 · 7 revisions

Code as data is the mantra. Functions and closures as data. So why not objects as data? What I propose is nothing new, but perhaps a new style.

Making objects from map structures is simple enough in Clojure. And it is easy enough to put functions in a map. So why not add closures? A closure in a map is a lot like an object method, hmm?

I found clojure components to be inspirational. But as light-weight as they are, components are still too heavy-weight to be objects. But a simple map seems ideal. And with only a map instead of a record or deftype, composition is simplicity itself. But the key idea here comes from clojure components: contents of the map should be configuration parameters or architecture, but not state. Put state in an atom and then (optionally) put the atom in the map. But once an object is formed, the contents of the map should not change. There should be no need to update a reference to the map.

Below is what I am calling a Clojure Object. Like a Java object, the map holds both data and methods (closures). Note that, because we are using closures, local data can be accessed without having to be put in the map. For example, the file-channel variable is not accessed via the map and need not have been added to the map.

Of course, as soon as you have a close method, you need to ensure that the method gets called when appropriate. So there needs to be something that tracks the close methods of the various mixins. Application logic can then call close without worrying about what mixins have been used. This is all handled by the Closer mixin.

Bill

(ns aatree.db-file
  (:require [aatree.closer :refer :all])
  (:import (java.nio.channels FileChannel)
           (java.nio.file OpenOption StandardOpenOption)))

(defn db-file-open
  ([file opts]
   (db-file-open (assoc opts :db-file file)))
  ([opts]
   (if (:db-file-channel opts)
     opts
     (do
       (if (not (:db-file opts))
         (throw (Exception. "missing :db-file option")))
       (let [file (:db-file opts)
             ^FileChannel file-channel
             (FileChannel/open (.toPath file)
                               (into-array OpenOption
                                           [StandardOpenOption/CREATE
                                            StandardOpenOption/READ
                                            StandardOpenOption/WRITE]))
             opts (assoc opts :db-file-channel file-channel)
             opts (on-close (fn [_] (.close file-channel)) opts)
             opts (assoc opts
                    :db-file-empty?
                    (fn []
                      (= 0 (.size file-channel))))
             opts (assoc opts
                    :db-file-read
                    (fn [byte-buffer position]
                      (.read file-channel byte-buffer position)))
             opts (assoc opts
                    :db-file-write
                    (fn [byte-buffer position]
                      (.write file-channel byte-buffer position)))
             opts (assoc opts
                    :db-file-write-root
                    (fn [byte-buffer position]
                      (.force file-channel true)
                      (.write file-channel byte-buffer position)
                      (.force file-channel true)))
             opts (assoc opts
                    :db-file-force
                    (fn []
                      (.force file-channel true)))
             ]
         opts)))))
Clone this wiki locally