Any comparison of hot JVM languages is likely to note that “Clojure is not object-oriented.” This is true, but it may lead you to the wrong conclusions. It’s a little like saying that a rifle is not arrow-oriented. In this article, you will see some of the ways that Clojure addresses the key concerns of OO: encapsulation, polymorphism, and inheritance.
This is a whirlwind tour, and we won't have time to cover the full details of all the Clojure code you will see. When we are done, I hope you will decide to explore for yourself. You can download and start using Clojure by following the instructions on the getting started page.
Just Enough Clojure Syntax
Clojure has
[1 2 3 4]
-> [1 2 3 4]
(get [:a :b :c :d :e] 2)
-> :c
In the preceding example, the initial [1 2 3 4] is input that you enter at the Read-Eval-Print Loop (REPL). The -> indicates the response from the REPL.
Clojure has
{:fname "Stu", :lname "Halloway"}
-> {:fname "Stu", :lname "Halloway"}
#{\a \e \i \o \u}
-> #{\a \e \i \o \u}
Lists are singly-linked lists, and are enclosed with parentheses. Lists are special: Not only are they data, they also act as the syntax for invoking functions. The list below invokes the plus (+) function:
(+ 1 2 3 4 5)
-> 15
Collections themselves act as functions. They take an argument which is the key/index to look up:
([:a :b :c :d :e] 2)
-> :c
({:name "Stu" :ext 101} :name)
-> "Stu"
Enough syntax, let's get started.
Encapsulation
Encapsulation is the hiding of implementation details so that clients of your code do not accidentally become dependent on them. In object-oriented languages, this is ususally done at the class level. A class has public methods, private implementation details, and various other scopes in between.
Clojure accomplishes the purposes of encapsulation in three ways: closures, namespaces, and immutability.
Closures
A closure closes over (remembers) the environment at the time it was created. For example, the function make-counter below closes over the initial value passed via init-val:
(defn make-counter [init-val]
(let [c (atom init-val)] #(swap! c inc)))
Let’s break this down:
defndefines a new function, namedmake-counter, that takes a single argumentinit-val.- The
letbinds the namecto a newatom. - The
atomcreates a threadsafe, deadlock-proof mutable reference to a value. - The octothorpe (
#) prefix introduces an anonymous function - The call to
swap!updates the value referenced bycby callingincon it. - The value of the let is the value of its last expression. This
letreturns a function that increments a counter, which is then the return value ofmake-counter.
The atom c is private to the function returned by make-counter. The only public thing you can do is increment it by one:
(def c (make-counter))
-> #'user/c
(c)
-> 1
(c)
-> 2
(c)
-> 3
The counter example returned a single function, but nothing stops you from returning multiple functions. These multiple functions can then share private state. The new version of make-counter below returns two functions: one to increment the counter, and one to reset it.
(defn make-counter [init-val]
(let [c (atom init-val)]
{:next #(swap! c inc)
:reset #(reset! c init-val)}))
This new make-counter returns a map whose :next value increments the counter, and whose :reset value resets it:
(def c (make-counter 10))
-> #'user/c
((c :next))
-> 11
((c :next))
-> 12
((c :reset))
-> 10
Why the double parentheses above? Two functions calls: The inner function call looks up the appropriate function, and the outer one calls it.
Closing over data is far more general than the simplistic model offered by private, protected, public, friend, et al. in OO languages. By combining multiple lets and multiple return values from a function, you can create arbitrary encapsulation strategies.
Similar encapsulation possibilities are available in any language that supports closures. Douglas Crockford describes a similar idiom in JavaScript.
Namespaces
A Clojure namespace groups a set of related data and functions. Inside a namespace, a Clojure var can refer to a function or to data, and can be public or private.
For example, Chris Houser’s error-kit library implements a condition/restart system for Clojure.
(with-handler
(vec (map int-half [2 4 5 8]))
(handle *number-error* [n]
(continue-with 0)))
In the code above, with-handler, handle, and continue-with are public vars of the clojure.contrib.error-kit namespace. The int-half is a demo function that blows up on odd inputs. When a *number-error* occurs, the handler causes execution to continue with the value 0. (Note how this is more flexible than try/catch exception handling, which cannot recover back into the middle of some operation.)
Internally, error-kit keeps track of available handlers and continues using these private vars:
(defvar- *handler-stack* ()
"Stack of bound handler symbols")
(defvar- *continues* {}
"Map of currently available continue forms")
The trailing minus sign on the end of defvar- marks the vars as private. These vars are implementation details, and are invisible to code outside the clojure.contrib.error-kit namespace.
Immutability
In OO languages, another purpose of encapsulation is to prevent object A from modifying or corrupting the private data used by object B.
In Clojure, this problem does not exist. Data structures are immutable. They cannot possibly be corrupted, or changed in any way, period. You can write query functions that return “private” state, without any fear of data corruption.
Polymorphism
For our purposes here, polymorphism is the ability to choose a different method implementation based on the type of the caller. So for example:
Flyer a = new Airplane();
Flyer b = new Bird();
a.fly();
b.fly();
a.fly() and b.fly() do different things because they are called on different concrete types.
Clojure provides a generalization of polymorphism called multimethods. A multimethod definition begins with defmulti, and then has a name, plus a dispatch function that is used to select the actual implementation: To mimic polymorphism, simply dispatch on the class of the argument:
(defmulti fly class)
Individual methods of a multimethod begin with defmethod, then the multimethod name, then the object that must match the dispatch function. Finally, you get the argument list in a vector, followed by the implementation of the method. For example:
(defmethod fly Bird [b] (flap-wings b))
(defmethod fly Airplane [a] (turn-propeller a))
Unlike polymorphism, multimethods do not limit you to dispatching on class. You can dispatch based on any arbitrary function of the method arguments. So for example, a bank account might have a :type entry that is used to determine the interest rate:
(defmulti interest :type)
(defmethod interest :checking [a] 0)
(defmethod interest :savings [a] 0.05M)
The :type attribute is a convention, but nothing prevents you from dispatching on a different attribute, or even dispatching on more than one at the same time! For example, the service-charge multimethod below dispatches on two different facets of the same object: the object’s account-level (::Basic or ::Premium) and its :tag: (::Checking or ::Savings)
(defmulti service-charge
(fn [acct] [(account-level acct) (:tag acct)]))
(defmethod service-charge [::Basic ::Checking] [_] 25)
(defmethod service-charge [::Basic ::Savings] [_] 10)
(defmethod service-charge [::Premium ::Checking] [_] 0)
(defmethod service-charge [::Premium ::Savings] [_] 0)
The _ is a legal name, and is used idiomatically to indicate that an argument will be ignored. (There is no need to even look at the argument, since all the work has been done in choosing which method to dispatch to!) This example also demonstrates two other concepts:
- The double-colon prefix resolves a keyword in a namespace. This prevents name collisions among keywords, just as object-oriented langauges use namespaces to prevent name collisions between type names.
account-levelis a function (not shown here), not a simple key lookup. It returns::Premiumor::Basicbased on the the account type and the current balance. Thus an account can dynamically change its account level as its balance changes.
As you can see, multimethods are far more general than polymorphism. Instead of being limited to type-based dispatch, multimethods can dispatch on any arbitrary function of an argument list. This allows programming models that more closely resemble reality: after all, what real-world entities are limited to a single type hierarchy, and forbidden to change types over time?
Inheritance
In OO languages, inheritance allows you to create a derived type that reuses the behavior of a base type. For example:
class Person {
String fullName() { /* impl details */ }
}
class Employee extends Person {
AddressBookItem companyDirectoryEntry() { /* impl details */ }
}
This kind of reuse is so natural in Clojure that it doesn’t even have a name. For example, here is a function that returns the full name of a person, based on first and last names:
(defn full-name [p]
(str (:first-name p) " " (:last-name p)))
Employees are like people, but have other properties and behaviors, such as a telephone extension. The company-directory-entry returns a vector of an employee's full name and telephone extension, like this:
(defn company-directory-entry [p]
[(full-name p) (:extension p)])
Notice that company-directory-entry “reuses” the person-ness of its argument p by calling full-name on it. There is no special inheritance ceremony required to set this up, you just call functions when you need them.
You can pass either a person or an employee to full-name. For company-directory-entry, though, you must have an employee. Or, more accurately, you must have something that resembles an employee, to the extent of having a :first-name, :last-name, and :extension. This is an example of duck typing: if it walks like a duck and quacks like a duck, we assume it is a duck, without asking it to present its IDuck papers.
Many Functions, Few Types
The example above demonstrates another negative consequence of idiomatic OO style: the over-specification of data types. The return value of companyDirectoryEntry is given its own unique type, AddressBookItem. Each new data type like AddressBookItem requires its own life-support system: constructors, accessors, equals, hashCode, and so on.
In Clojure, an address book item would simply be a vector or a map. No new types, and no life support system required. Moreover, an address book item can be manipulated with any of the large arsenal of functions in Clojure's sequence library.
To see the problem with overspecifying types, consider this method from the Apache Commons:
// From Apache Commons Lang, http://commons.apache.org/lang/
public static int indexOfAny(String str, char[] searchChars) {
if (isEmpty(str) || ArrayUtils.isEmpty(searchChars)) {
return -1;
}
for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i);
for (int j = 0; j < searchChars.length; j++) {
if (searchChars[j] == ch) {
return i;
}
}
}
return -1;
}
The purpose of indexOfAny is to find the index of the first occurrence of one of the searchChars that appears in str. Note the unnecessary specificity of types: it works only with strings and character arrays.
Here's the Clojure version, using the sequence library's map, iterate, and for forms:
(defn indexed [coll] (map vector (iterate inc 0) coll))
(defn index-filter [pred coll]
(when pred
(for [[idx elt] (indexed coll) :when (pred elt)] idx)))
Here is an example calling index-filter:
(index-filter #{\a \e \i \o \u} "Lts f cnsnts nd n vwel")
-> (20)
The expression above finds the index of the first vowel in the string "Lts f cnsnts nd n vwel", that is, 20. But index-filter is more general than the Commons version in several ways:
1. index-filter returns all the matches, not just one.
(index-filter #{\a \e \i \o \o} "The quick brown fox")
-> (2 6 12 17)
2. index-filter works with any sequence, not just a string of characters. For example, the call below works against a range of integers:
(index-filter #{2 3 5 7} (range 6))
-> (2 3 5)
3. index-filter works with any predicate, not just a test against a character array. In the example below, the predicate is an anonymous function that tests for strings longer than three characters:
(index-filter #(> (.length %) 3) ["The" "quick" "brown" "fox"])
-> (1 2)
That is a lot of extra power, especially given that the function is shorter, easier to write, and easier to read (given some Clojure experience, of course) than the Commons version.
Conclusion
Clojure solves the same problems that OO solves, but it solves them in different ways. Instead of encapsulation, polymorphism, and inheritance, you have closures, namespaces, pure functions, immutable data, and multimethods. Idiomatic OO gives you a bloated type system with duplicated code hidden away behind encapsulation boundaries and little hope for thread safety. Clojure offers a radical alternative: a lean type system, a rich function library, and language-level concurrency support that is usable by mere mortals.
There is a lot more to Clojure than we have covered here: lazy and infinite sequences, destructuring, macros, software transactional memory, agents, seamless Java interop, and more. But those are topics for another day.
[This article was originally published in the May 2009 issue of NFJS, the Magazine. I will be speaking about Clojure at several upcoming NFJS events, come join the fun.]
Comments
I found the simple make-counter example interesting. As a Groovy developer I am used to having access to closures. In Groovy you can accomplish something close to your make-counter with something like this…
makeCounter = { initialVal -> val = initialVal [next: { ++val }, reset: { val = initialVal }] }
To see that in action in a shell…
groovy:000> makeCounter = { initialVal -> groovy:001> val = initialVal groovy:002> [next: { ++val }, reset: { val = initialVal }] groovy:003> } groovy:000> c = makeCounter(10) groovy:000> c.next() ===> 11 groovy:000> c.next() ===> 12 groovy:000> c.reset() ===> 10
I expect that the Ruby counterpart probably looks very close to that.
I really find Clojure interesting. One of the things Clojure is doing is bringing the power of Lisp to a whole new group of folks who have never done any Lisp. I realize that a lot of Clojure developers are long time Lispers but I know that a lot of JVM developers are coming to Lisp via Clojure. The process of OO developers learning the ways of functional programming is underway in no small part thanks to Clojure.
“Any comparison of hot JVM languages is likely to note that “Clojure is not object-oriented.””
That sentence has 6 hits in Google, and one of them is this page. One is Hickey who clarifies with “in the traditional sense”, and one qualifies it with “except when it is”. I don’t think that’s as true as you think. :-)
Besides, if you have multimethods, I don’t know how you can claim to be not object-oriented. Even Norvig, old Lisp fart, said of Common Lisp that “Depending on your definition, CLOS is or is not object-oriented. It doesn’t support encapsulation”—no mention of multimethods being a limiting factor here.
The statement in “Many Functions, Few Types” contradicts the rest of the article a little bit. If you use a map, you are free to access the items without any restriction. There is no encapsulation as there would be in Java with the life support systems – each setter would make sure the data is consistent. Or there would be no setter, just a getter.
And no, I’m not against Clojure. I have already written more than 1000 lines of clj code :)
make-counter is a bad example about simplicity
you could achive this even in c with a simple increment operator:
int i; i++; i++; ...
The “over-specification of data types” is optional and there is a template system to achive the same thing as in your indexOfAny example.
C is one of the most unreadable but best performing languages there are. Wouldn’t it be better to compare it with java or python?
Nice Walkthroug to Clojure, tho.
The problem with all data structures being immutable is that it is a major pain to do any complex object graph manipulations.
Just coming off a project with Erlang which has a similar philosophy…doing complex record manipulation (with nested data structures) with 100% immutable records was a pain with a capital P. What would have been a few lines of Java turned into literally hundreds of lines of complex code (‘cause every time you change the property of a nested child, you have to recreate all of its parents into a new immutable data structure). Very error prone too. I understand immutable data structures are great for concurrency, but for sequential programming they are like a trip to programming hell.
At first glance I think I prefer the approach that Scala has to give you an option of immutable or mutable, depending on the scenario. Just my $0.02…
I think this is an excellent explanation of how Clojure addresses the same concerns of OO in a different manner. Thank you!
In the first example, I think you want `(def c (make-counter 0))` instead of just `(def c (make-counter))`.
Hannes: ++ in C is completely different.
First, how would you implement it? I certainly can’t write ++ in C in 2 lines of code. (It’s built-in, but every built-in is going to be simpler than a homemade one. That’s true of every language feature in every language, and has nothing to do with the flexibility he’s describing here.)
Second, how do you pass + to a function? + is syntactic sugar, but not even as powerful as a normal C function.
Third, ++ doesn’t even increment its argument. It increments its argument, and then takes the modulo with some other value (a constant in limits.h, IIRC).
Jacek: Don’t write the functional transformations by hand! Use a tree-walker, or the Clojure zipper, or roll-your-own XPath-like syntax for transformations.
Hannes: What Mike said! Plus, C increment can’t be shared across threads in any simple way.
Roman: validations are orthogonal to data, and Clojure makes it easy to see this.
Mike: most OO assumes that mutable state is reached through object identity. Clojure does not make (or like!) this assumption.
@Jacek I’ll second Stuart’s urging to use something for transformations. Zippers are pretty darn nice, and they’re in the clojure standard library (though I prefer them paired with Chris Houser’s excellent zip-filter lib in clojure.contrib.zip-filter—such things make tree transformation and searches dead simple, and very fast).
Basically, I think, Rich Hickey has expressed the Clojure attitude very well in http://clojure.org/state. Yet, reframing (http://en.wikipedia.org/wiki/Reframing) is a useful marketing technique, but it’s not a true argument, where each side has it’s point. So I wanted to ask a couple of questions, if it’s possible…
“Clojure solves the same problems that OO solves, but it solves them in different ways. Instead of encapsulation, polymorphism, and inheritance, you have closures, namespaces, pure functions, immutable data, and multimethods.” What of the the four substitute inheritance?
“Idiomatic OO gives you a bloated type system with duplicated code hidden away behind encapsulation boundaries and little hope for thread safety. Clojure offers a radical alternative: a lean type system,” What is exactly lean about the Clojure type system on the contrary to the bloated OO type-systems? (Especially, since Clojure AFAIU uses the underlying Java OO type-system)
And what do you mean by rifle-oriented programming?
Stuart just wanted to say a big thank you for this! It is really useful for someone like me coming from a Java OO background and just starting out with Clojure.
Thanks again, James
@Vsevolod – Rifle-oriented programming is a joke from the first paragraph – “It’s a little like saying that a rifle is not arrow-oriented.” A rifle does not work like an arrow, but accomplishes the same task (generally, much more effectively). The implication is that while Clojure may not be object-oriented, it accomplishes the same task (more effectively).
Vsevolod: Clojure doesn’t make you use and extend the type system for everything as Java does, it exposes a different way to write and organize your code.
Concerning, inheritance, Stuart’s example demonstrates you don’t need a new OO type to solve that exampe problem. In addition, multi-methods can work with the ad hoc hierarchy mechanism: http://clojure.org/multimethods.
I think rifle-oriented is just the analogy with arrows, implying that functional is more powerful than OO.
Stuart: Excellent article – it’s good to show how the familiar problems can be solved.
Guys, thanks for the explanation about rifles, but you see, if you want to draw parallels, it’s not rifle and arrow, but rather rifle and bow, no? Yet, anyway, do such metaphors have any technical merit? And concerning the technical points: if you can do something differently, does it mean, that the new way is obviously superior to the old? Isn’t it rather a question of concrete demands, domains, experiences and tastes?.. (That’s why I brought Hickey’s article: it just explains, what are the principles, that his approach follows and why, no comparison of apples to oranges or arguing why you don’t need apples, if you have oranges)
@Vsevolod: substitute inheritance (I guess you are referring to Liskov’s substitution principle) only works in OO systems where a subclass is a subtype. Clojure does not insist on this constraint.
[Part II – forgot to add]
@Vsevolod: I agree, rifle and bow is a better analogy. No, I do not think the metaphor was for a technical use, more for the “advocacy”.
As far as which way is superior, I would say it depends on the application area and problem at hand. However, I think it can be safely said that the standard C++/Java way of doing OOP with multiple classes and deep class hierarchies tends to be overdone in many concrete projects (and also tends to be cumbersome, as mentioned in the article). Lisp has defined functional (multimethod-based) OOP long ago, so the latter has been used simultaneously (I mean, contemporaneously) with the standard OOP for decades. However, Lisp was used in a niche programming (AI, etc) and thus I do not know if direct comparison of past experience is possible. (I am talking about developers’ experience.)
Vsevolod: don’t ask too much of a joking analogy. :
) But since you ask, (1) I chose rifle/arrow over rifle/bow because the imagery was more important to me than the parallelism of the analogy. (2) Yes, rifle implies more powerful-but not in all circumstances. That ambiguity is part of why I like the metaphor.