Does there exist anything like CLOS (Common Lisp Object System) for Clojure?
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.
Clojure itself doesn't have an object system, for two reasons:
- 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.
- 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.)
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.
- http://clojure.org/datatypes (look at defrecord --> the best of classes and hash-maps)
- http://clojure.org/protocols (kind of like interfaces but better)
- http://clojure.org/multimethods (powerful because you can write your own dispatch functions)
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.
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}]
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.)
CljOS is a toy OOP library for Clojure. It is by no sense of the word complete. Just something I made to have fun.
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.
© 2022 - 2024 — McMap. All rights reserved.
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