CLOS for Clojure?
Asked Answered
F

7

25

Does there exist anything like CLOS (Common Lisp Object System) for Clojure?

Fleming answered 28/10, 2010 at 19:9 Comment(0)
A
19

Have you considered Clojure's data types (especially defrecord), protocols, and multimethods? All three will always be more idiomatic within Clojure than a port of CLOS on top of these mechanisms.

Anechoic answered 28/10, 2010 at 19:52 Comment(4)
Then again, if we all listened when somebody said "don't do this, it's dumb", Steve Russell might have listened to John McCarthy and never hand-compiled his eval, and we might never have had Lisp on a computer at all. Thus began a long history of Lisp hackers who had no respect for what somebody else said was the right thing to do! So I say, go for it, port CLOS to Clojure, and see what happens. Really, what's the worst that could happen? :-)Lenity
I'm not saying, "don't reimplement CLOS". I'm just saying that the facilities provided and supported by the core of a language should at least be considered for a given use before attempting to treat a horse like a cow, as it were. Now, if one is brave (/foolish?), all sorts of object systems could be built on top of those core facilities: certainly CLOS if one were so motivated, and likely far more advanced paths as well.Anechoic
@Anechoic default data types lacks inheritance. isa+multimethods is the closest one, yet you need to inherit fields yourself.Fructiferous
CLOS dispatch mechanism sounds really complex and a bit of an overkill. Clojure protocols don't go that far and multimethods do not seem to have access to all the information needed.Cyanate
Z
17

Clojure itself doesn't have an object system, for two reasons:

  1. Clojure is specifically designed to be hosted on an object-oriented platform and it then simply absorbs the underlying platform's object system. I.e. ClojureJVM has the JVM object system, ClojureCLR has the CLI object system, ClojureScript has the ECMAScript object system, and so on.
  2. Rich Hickey hates objects.

But, you can obviously implement an object system in Clojure. Clojure is, after all, Turing-complete.

Mikel Evins is working on a new approach to OO which he calls Categories. He has implementations for several Lisps, including Clojure (although not all the ports are guaranteed to be up-to-date all the time).

Categories is slowly being subsumed by Bard, a new Lisp dialect that Mikel is designing, which has Categories built in. (Which then, in turn, may become the implementation language for Closos, an idea Mikel had for how to design an operating system.)

Zebrawood answered 28/10, 2010 at 20:48 Comment(1)
"Rich Hickey hates objects." It is my understanding that one of the "beauties" of the Lisp family is that the programmer can extend the language into anything she desires, even if the original author did not (or does not approve :-)). My hat's off to Rich for a great language design, but I bet he will agree that allowing the user to add a full object system is one of the strengths of Clojure (or any Lisp).Seligman
R
12

Clojure does not have CLOS and doesn't want CLOS but you could implement it.

Clojure wants to be immutable so to have mutable OO would be kind of stupid, but you can have a kind of OO.

With these three things you should be able to fulfill all your needs, but most of the time, its best to just use normal functions and the standard data structures.

Rabbinate answered 28/10, 2010 at 20:1 Comment(1)
I believe every language should have a beginner mode and an expert mode. In beginner mode, "dangerous" features like OO can be excluded, but the expert should be allowed to use these features in limited circumstances (to be determined by the expert). For example, Java does not support C-style macros because James Gosling, et al, think they are dangerous. Although there are work-arounds, experts sometimes curse their absence.Seligman
S
4

Using the OO paradigm is ideal for writing loosely coupled code, mocking and testing. Clojure makes this so easy to accomplish.

One problem that I had ran into in the past was code depending on other code. Clojure namespaces actually exacebate the problem if not used well. Ideally, namespaces can be mocked out but as I have found... there are a lot of problems with mocking out namespaces:

https://groups.google.com/forum/?fromgroups=#!topic/clojure/q3PazKoRlKU

Once you start building bigger and bigger applications, the namespaces start depending upon each other and it gets really unweldy to test your higher-level components seperately without having a bunch of dependencies. Most of the solutions involve function re-binding and other black magic but the problem is that come testing time, the original dependencies are still being loaded -> which grows into big problem if you have a big application.


I was motivated to to look for alternatives after using database libraries. Database libraries have brought me so much pain - they take ages to load and usually are at the core of your application. Its very difficult to test your application without bring a whole database, the library and associated peripherals into your test code.

You want to be able to package your files so that parts of your system that depend on your database code can be 'swapped out'. OO design methodology provides the answer.

I'm sorry the answer is quite long... I wanted to give a good rationale for why OO design is used more so than how it is used. So an actual example had to be used. I have attempted to keep the ns declarations so that the structure of the example application will be kept as clear as possible.

existing clojure style code

This example uses carmine, which is a redis client. It is relatively easy to work with and is quick to start up compared to korma and datomic, but a database library is still a database library:

(ns redis-ex.history
  (:require [taoensso.carmine :as car]
            [clojure.string :as st]))

(defmacro wcr [store kdir f & args]
  `(car/with-conn (:pool ~store) (:conn ~store)
     (~f (st/join "/" (concat [(:ns ~store)] ~kdir)) ~@args)))

(defn empty [store kdir]
  (wcr store kdir car/del))

(defn add-instance [store kdir dt data]
   (wcr store kdir car/zadd dt data))

(defn get-interval [store kdir dt0 dt1]
  (wcr store kdir car/zrangebyscore dt0 dt1))

(defn get-last [store kdir number]
  (wcr store kdir car/zrange (- number) -1))

(defn make-store [pool conn ns]
{:pool pool
 :conn conn
 :ns ns})

existing test code

all the functions should be tested... this is nothing new and is standard clojure code

(ns redis-ex.test-history0
   (:require [taoensso.carmine :as car]
             [redis-ex.history :as hist]))

(def store
  (hist/make-store
   (car/make-conn-pool)
   (car/make-conn-spec)
   "test"))

(hist/add-instance store ["hello"] 100 100) ;;=> 1
(hist/get-interval store ["hello"] 0 200) ;;=> [100]

object orientated dispatch mechanism

The idea that 'OO' isn't evil but actually quite useful came to me after watching this talk Misko Hevery:

http://www.youtube.com/watch?v=XcT4yYu_TTs

The basic idea is that if you want to build a big application, you have to separate the 'functionality' (the guts of the program) from the 'wiring' (the interfaces and the dependencies). The less dependencies the better.

I use clojure hash-maps as 'objects' because they have no library dependencies and are completely generic (see Brian Marick talking about using the same paradigm in ruby - http://vimeo.com/34522837).

To make your clojure code 'object orientated' you need the following function - (send stolen from smalltalk) which just dispatches a function associated with a key in a map if it is associated with an existing key.

(defn call-if-not-nil [f & vs] 
   (if-not (nil? f) (apply f vs))

(defn send [obj kw & args] 
   (call-if-not-nil (obj kw) obj))

I provide the implementation in a general purpose utility library (https://github.com/zcaudate/hara in the hara.fn namespace). It's 4 lines of code if you want to implement it for yourself.

defining the object 'constructor'

you can now modify the original make-store function to add functions in the map. Now you have a level of indirection.

;;; in the redis-ex.history namespace, make change `make-store`
;;; to add our tested function definitions as map values.

(defn make-store [pool conn ns]
  {:pool pool
   :conn conn
   :ns ns
   :empty empty
   :add-instance add-instance
   :get-interval get-interval
   :get-last get-last})

;;; in a seperate test file, you can now test the 'OO' implementation

(ns redis-ex.test-history1
   (:require [taoensso.carmine :as car]
             [redis-ex.history :as hist]))
(def store
   (hist/make-store
   (car/make-conn-pool)
   (car/make-conn-spec)
   "test"))

  (require '[hara.fn :as f])
  (f/send store :empty ["test"])
  ;; => 1

  (f/send store :get-instance ["test"] 100000) 
  ;; => nil

  (f/send store :add-instance ["test"]
   {100000 {:timestamp 1000000 :data 23.4}
    200000 {:timestamp 2000000 :data 33.4}
    300000 {:timestamp 3000000 :data 43.4}
    400000 {:timestamp 4000000 :data 53.4}
    500000 {:timestamp 5000000 :data 63.4}})
  ;; => [1 1 1 1 1]

build abstraction

so because the make-store function constructs a store object which is completely self contained, functions can be defined to take advantage of this

(ns redis-ex.app
   (:require [hara.fn :as f]))

(defn get-last-3-elements [st kdir]
   (f/send st :get-last kdir 3))

and if you want to use it... you would do something like:

(ns redis-ex.test-app0
  (:use redis-ex.app 
        redis-ex.history)
  (:require [taoensso.carmine :as car]))

(def store
   (hist/make-store
   (car/make-conn-pool)
   (car/make-conn-spec)
   "test"))

(get-last-3-elements ["test"] store) 
;;=> [{:timestamp 3000000 :data 43.4} {:timestamp 4000000 :data 53.4} {:timestamp 5000000 :data 63.4}]

mocking with clojure - 'OO' style

So the real advantage of this is that the get-last-3-elements method can be in a completely different namespace. it does not depend on the database implementation at all and so testing this function now only requires a lightweight harness.

mocks are then trivial to define. Testing of the redis-ex.usecase namespace can be done without loading in any database libraries.

(ns redis-ex.test-app1
  (:use redis-ex.app))

(defn make-mock-store []
   {:database [{:timestamp 5000000 :data 63.4} 
               {:timestamp 4000000 :data 53.4}
               {:timestamp 3000000 :data 43.4} 
               {:timestamp 2000000 :data 33.4} 
               {:timestamp 1000000 :data 23.4}]
    :get-last (fn [store kdir number] 
                  (->> (:database store)
                       (take number)
                       reverse))})

(def mock-store (make-mock-store))
(get-last-3-elements ["test"] mock-store)
;; => [{:timestamp 3000000 :data 43.4} {:timestamp 4000000 :data 53.4} {:timestamp 5000000 :data 63.4}]
Sadiesadira answered 30/11, 2012 at 7:15 Comment(3)
Quite so. This is how it is done in Scheme. The "object constructor" is a function that returns a method dispatch function, which stands for the object. "Sending a message to the object" is calling that dispatch function with the appropriate method name and parameters. You may then receive a new dispatch function to replace the "old object" if the object's inner state was modified by the method call....Cyanate
... The methods held by the dispatch function which are closures which have access to the context valid at dispatch function creation time, which is corresponds to object fields in Java, only inaccessible as there is no way to access that context directly. In Clojure, the dispatch function may be replaced by a map (which can be used like a function, but looks better, and can be easily patched). At little example here based on code from Joy of Clojure.Cyanate
yep. exactly. that example is great.Sadiesadira
A
3

The earlier posts address the question as a question about the value and possibilities of implementing specific support for various features of object-oriented programming in Clojure. However, there is a family of properties that are associated with that term. Not all object-oriented languages support all of them. And Clojure directly supports some of these properties, whether you want to call that support "object-oriented" or not. I'll mention a couple of these properties.

Clojure can support dispatch on hierarchically defined types using its multimethod system. The basic functions are defmulti and defmethod. (Maybe these weren't available when the question was first answered.)

One of the relatively unusual features of CLOS is its support for functions that dispatch on the types of multiple arguments. Clojure emulates that behavior very naturally, as an example here suggests. (The example doesn't use types per se--but that's part of the flexibility of Clojure's multimethods. Compare with the first example here.)

Arnettearney answered 2/11, 2013 at 19:42 Comment(0)
S
0

CljOS is a toy OOP library for Clojure. It is by no sense of the word complete. Just something I made to have fun.

Shani answered 19/10, 2014 at 15:27 Comment(0)
U
-3

It's an old post but I wanted to respond to it.

No clojure has no OO support, and no CLOS support. The underlying object system of the environment is only barely available in the sence of interoperability, not for making your own class/objects hierarchies in clojure. Clojure is made for easy access to CLR or JVM libraries, but OOP support end here.

Clojure is a lisp and support closures & macros. With thoses 2 features in mind, you can develop a basic object system in a few lines of code.

Now the point is do you really need OOP in a lisp dialect ? I would say no and yes. No because most problem can be solved without an object system and more elegantly in any lisp. I would say yes, because you'll still need OOP from time to time and it is then better to provide a standard reference implementation than having every geek implementing it's own.

I would recommand that you take a look at On Lisp book, from Paul Graham. You can consult it free of charge online.

This is really a good book, that really grasp the essence of lisp. You'll have to adapt the syntax a little to clojure, but concepts remain the same. Important for your question, one of the last chapter show how to define your own object system in lisp.

A side remark, clojure embrace immutability. You can make a mutable object system in clojure, but if you stick to immutability, you design, even using OOP will be quite different. Most standard design pattern and construction are made with mutability in mind.

Unheard answered 21/7, 2011 at 15:49 Comment(1)
> "No clojure has no OO support," .... "The underlying object system of the environment is only barely available in the sence of interoperability, not for making your own class/objects hierarchies in clojure" That's definitely not true. See data types (especially defrecord), protocols, and multimethods, reify, AOT, etc. You can definitely implement an interface, say Iterator, in Clojure. Even in a REPL.Possum

© 2022 - 2024 — McMap. All rights reserved.