Clojure Objects

450 views
Skip to first unread message

William la Forge

unread,
Nov 20, 2015, 9:54:15 PM11/20/15
to Clojure

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 easy enough to put functions in a map. So why not 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 this map.

Below is what I am calling a Clojure Object. Like a Java object, the method 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.

Bill

(ns aatree.db-file
  (:require [clojure.tools.logging :as log])
  (: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 (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 (assoc opts
                :db-close
                (fn []
                  (try
                    (.close file-channel)
                    (catch Exception e
                      (log/warn e "exception on close of db-file")))
                  (dissoc opts :db-file-channel)))
         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)))

William la Forge

unread,
Nov 20, 2015, 10:01:25 PM11/20/15
to Clojure
Oh! Some minor edits. Which can be found here: https://github.com/laforge49/aatree/wiki/Clojure-Objects

James Reeves

unread,
Nov 20, 2015, 10:28:49 PM11/20/15
to clo...@googlegroups.com
What's the benefit to writing code like this?

The only thing I could possibly see as being considered an advantage is that it encapsulates the file channel, but you've exposed that via a key anyway.

- James

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

William la Forge

unread,
Nov 20, 2015, 11:50:16 PM11/20/15
to Clojure, ja...@booleanknot.com
James,

The advantages of one style over another are often subtle. And indeed, a single object written this way has no real advantage. Poor choice, but it was the only code I have written in this way so far. The addition of closures only occurred to me while writing this piece of code.

I included the file-channel in the map more on principle than anything else. Opinionated Clojure does not try to encapsulate the way Java does, or such has been my impression anyway. I do sometimes encapsulate when I want the API to be clearer, but otherwise not.

The reason for having this "type of object" at all is that I was going to have 3 copies of the same code. Which I find to be a bad thing.

One thing I left out was that in aatree.core I have functions which call the "object methods". These functions are implemented like this: 

(defn do-something [arg1 arg2 opts] ((:do-something opts) arg1 arg2))

This gives me a lot of decoupling. opts then is the "object" map. But it can be a munged together map of lots of key/value pairs from a lot of different "types of objects". Composition goes something like this in its simplest form:

(opts -> (db-file-open) (db-cache-open) etc)

I end up composing a lot of things richly but with simplicity. Being new to Clojure, I am fascinated by all this. And as I add more and more capabilities, it looks like everything stays quite stable--which is exactly the opposite of my experience in working with Java objects.

Bill

William la Forge

unread,
Nov 21, 2015, 12:00:03 AM11/21/15
to Clojure, ja...@booleanknot.com
You can tell I'm still new to clojure. The composition should have been written like this:

(-> opts (db-file-open) (db-cache-start) etc)

Timothy Baldridge

unread,
Nov 21, 2015, 12:29:34 AM11/21/15
to clo...@googlegroups.com
You might want to read up on records and protocols in clojure. This is pretty much the use case for which they were designed.

Timothy


On Friday, November 20, 2015, William la Forge <lafo...@gmail.com> wrote:
You can tell I'm still new to clojure. The composition should have been written like this:

(-> opts (db-file-open) (db-cache-start) etc)

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.


--
“One of the main causes of the fall of the Roman Empire was that–lacking zero–they had no way to indicate successful termination of their C programs.”
(Robert Firth)

William la Forge

unread,
Nov 21, 2015, 12:53:52 AM11/21/15
to Clojure
The advantage of maps over records is if I have 3 objects as maps I can easily munge them into a single map. But if I have 3 objects as records, I loose that option. OK, I can nest records inside each other but it is not the same. With objects as maps I've got something closer to mixins. But not type based.

Hmm. Guess the mixins thing is the real advantage. But mixins into objects rather than extending a type with functions.

Now I did find this:

Function maps are maps of the keywordized method names to ordinary fns
  • this facilitates easy reuse of existing fns and maps, for code reuse/mixins without derivation or composition

The above was taken from http://clojure.org/protocols
where it was talking about extend.

It is very similar. But it is more about extending the polymorphic methods. While I'm talking about extending an object. And aggregating in additional data as well as methods.


You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/N-UG4jh8FDY/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

William la Forge

unread,
Nov 21, 2015, 1:13:46 AM11/21/15
to Clojure
Timothy,

I've been thinking about this a bit more and I see that you can supply data via a function in a function map that is part of extend. And while self reference between the various parts of a composite can get awkward, you can always revert to a function to complete that self-reference the same way I do with my core functions which have no knowledge of the implementations involved, except that you may be forced to do reflection.

I think the approach I am proposing is simpler both in that it avoids some of the boiler plate (thought I'd never say that about Clojure!) and completely bypasses the need for reflection. Within a composite (where opts is the name of the composed map) you can easily call any "method" like this: ((:some-method opts) arg1 arg2) without worrying about the order in which things are defined--one of the big weaknesses of Clojure compared to Java. Of course, now there is even less type checking that even the little that Clojure normally provides. But again, you can always define simple functions so that you don't end up invoking closures all over the place via (:method-key opts).

In any case, I'm having fun. And while the approach is not the fastest, I seem to be producing rock-solid stable code. Which for me is a great big plus!

James Reeves

unread,
Nov 21, 2015, 11:54:21 AM11/21/15
to William la Forge, Clojure
On 21 November 2015 at 04:50, William la Forge <lafo...@gmail.com> wrote:
The reason for having this "type of object" at all is that I was going to have 3 copies of the same code. Which I find to be a bad thing.

Could you explain why you were going to have duplicate code, and how your object system solved this?

All the examples you've provided are longer than they would be if they were written normally.

- James

William la Forge

unread,
Nov 21, 2015, 12:44:28 PM11/21/15
to Clojure, lafo...@gmail.com, ja...@booleanknot.com
Hi James!

I'll be the first to admit that I do not yet have a strong case here. And yeah, it looks like I'm introducing some boilerplate myself to do things this way. Which probably just means that I need to learn how to write macros or some such. :-)

I was just saying that the calf and yearling databases both do file io and I was going to have more of the same in the heifer database. So my duplicate code argument was a bit bogus, as in-line code is always cleaner and faster.

I am looking now at instance mixins for atoms and channels. Should be an interesting experiment. Calf and yearling both are based on an atom and I need to switch to channels in heifer as I want to pipeline the transaction log. So I am thinking of first writing an atom mixin, then reworking calf and yearling to use it, and then replacing the atom mixin with a channel mixin. Instance composition gone crazy. :D

Bobby Bobble

unread,
Nov 23, 2015, 7:34:44 AM11/23/15
to Clojure
let's not forget that Clojure's datastructures are objects. They respond to messages like seq, first, rest etc (which requires a bit more complexity than what Clojurians hail as "just data", which would be like 1010101111110101011111000110000011...what Clojurians really mean by that term is something like "uniform access to objects")

(Clojure's vocabulary is not to be questioned...why say "conflate" or "confuse" when you can say "complect" to reinforce in-group membership ?) /rant

anyway to the point, it depends what you mean by "objects".

What you propose is to use maps as a kind of namespace for functions. It's a good idea. I use this pattern too. Really, namespaces should be maps...actually I think there was a project called Kiss to that end.

But real OO, as realized by Smalltalk and Self is about messaging, not objects. AFAIA there's no way in Clojure to represent "the invocation of a function on a thing" the way you can represent "the sending of a message to an object" in Smalltalk.

Gregg Reynolds

unread,
Nov 23, 2015, 5:24:10 PM11/23/15
to clo...@googlegroups.com


On Nov 23, 2015 6:34 AM, "Bobby Bobble" <bpb...@gmail.com> wrote:
>
> let's not forget that Clojure's datastructures are objects. They respond to messages like seq, first, rest etc (which requires a bit more complexity than what Clojurians hail as "just data", which would be like 1010101111110101011111000110000011...what Clojurians really mean by that term is something like "uniform access to objects")

Hmm.  If by "object" you mean entity, thing, value, etc. - i.e. mathematical structure - then I'd agree, but in my view Clojure does not involve OO "objects" in any way, _conceptually_.   "This function is defined on that type" != "that type 'responds to' this function" . 

>
> (Clojure's vocabulary is not to be questioned...why say "conflate" or "confuse" when you can say "complect" to reinforce in-group membership ?) /rant

THANK YOU!  I can't count the number of times I've had to restrain myself from an apoplectic rant about this hideous non-word.  What is wrong with "complicate" FFS?!

>
> anyway to the point, it depends what you mean by "objects".

>
> What you propose is to use maps as a kind of namespace for functions. It's a good idea. I use this pattern too. Really, namespaces should be maps...actually I think there was a project called Kiss to that end.

To the OP's original point: you can view functions as data, but I find myself moving toward a view of data as functions.  E.g. it's as nullary fns.


>
> But real OO, as realized by Smalltalk and Self is about messaging, not objects.

Thank you again.  Objective C is another good example.  Dunno why they called it OO in the first place.

AFAIA there's no way in Clojure to represent "the invocation of a function on a thing" the way you can represent "the sending of a message to an object" in Smalltalk.

Not quite sure what you mean, but with stuff like core.async and protocols it's possible to support an explicit msg - passing idiom in Clojure.  But I think I agree with you.  It would be something you build on more primitive notions.

Gregg

Gregg Reynolds

unread,
Nov 23, 2015, 5:26:45 PM11/23/15
to clo...@googlegroups.com


On Nov 23, 2015 4:23 PM, "Gregg Reynolds" <d...@mobileink.com> wrote:
>
>
> On Nov 23, 2015 6:34 AM, "Bobby Bobble" <bpb...@gmail.com> wrote:
> >
> > functions.  E.g. it's as nullary fns.

Sorry, "ints", not "it's ".  How I hate spell-checkers!!

Colin Yates

unread,
Nov 24, 2015, 3:19:41 AM11/24/15
to clo...@googlegroups.com

> (Clojure's vocabulary is not to be questioned...why say "conflate" or "confuse" when you can say "complect" to reinforce in-group membership ?) /rant

THANK YOU!  I can't count the number of times I've had to restrain myself from an apoplectic rant about this hideous non-word.  What is wrong with "complicate" FFS?!


Careful - ‘complect’ has a very specific meaning which is compatible with the notion of conflate. Confusion on the other hand is far more about familiarity. The whole point of the Simple made Easy talk was to propose that the ‘complexity’ of something is a mathematical measurement (loosely, the number of concerns in that atomic thing). Simple on the other hand was about your familiarity with the thing. 

Confusion is somewhat orthogonal to complexity as it is more to do with the _understanding_ of the thing.

Bobby Bobble

unread,
Nov 24, 2015, 6:17:45 AM11/24/15
to Clojure


On Tuesday, November 24, 2015 at 8:19:41 AM UTC, Colin Yates wrote:

> (Clojure's vocabulary is not to be questioned...why say "conflate" or "confuse" when you can say "complect" to reinforce in-group membership ?) /rant

THANK YOU!  I can't count the number of times I've had to restrain myself from an apoplectic rant about this hideous non-word.  What is wrong with "complicate" FFS?!


Confusion is somewhat orthogonal to complexity as it is more to do with the _understanding_ of the thing.


indeed. I meant "confuse" as "to make harder to understand" because one aim of coding is to make our code readable, and that comes with simplicity. Our code reflects our understanding of the problem and if someone's code is confusing, it's usually because they didn't understand the problem well enough to articulate a good solution.

Bobby Bobble

unread,
Nov 24, 2015, 9:23:40 AM11/24/15
to Clojure
"Careful - ‘complect’ has a very specific meaning"

OK something to do with braiding, yes.

But, if someone has to explain the etymology of their word to you for it to make sense, then the word has failed.

If you mean "braided" say "braided",  or better still "tangled"! I mean, "braided" or "plaited" imply pattern and forethought, not the messiness the word "complect" is supposed to imply.

Timothy Baldridge

unread,
Nov 24, 2015, 9:31:47 AM11/24/15
to clo...@googlegroups.com
>> But, if someone has to explain the etymology of their word to you for it to make >> sense, then the word has failed. 

If I took that approach with my kids, they'd never get out of first-grade. 

Timothy

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups "Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojure+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Nahuel Greco

unread,
Nov 24, 2015, 9:34:47 AM11/24/15
to clo...@googlegroups.com
Maybe a better word for complecting is "entangling". 

Saludos,
Nahuel Greco.

William la Forge

unread,
Nov 24, 2015, 10:21:45 AM11/24/15
to Clojure
As in quantum entanglement? :-)

Bobby Bobble

unread,
Nov 24, 2015, 10:50:22 AM11/24/15
to Clojure
spooky action at a distance - back to OO again!

On Tuesday, November 24, 2015 at 3:21:45 PM UTC, William la Forge wrote:
As in quantum entanglement? :-)
Reply all
Reply to author
Forward
0 new messages