Clojure, from a Ruby perspective

Fogus' recent article "clojure.rb" speculates about why there seem to be so many Ruby users adopting Clojure. As a Ruby user who adopted Clojure, I figured I'd write about my experiences.

What do Ruby and Clojure have in common, that would attract a Rubyist to Clojure? A lot. Obviously, this is somewhat subjective and I don't expect anyone else to agree, but this is what did it for me.

Semantic consistency

In Ruby, everything is an object. It makes it simple to write code without worrying much about what kind of thing you have. foo.some_method(1,2,3) will generally work for any foo.

In Clojure, everything is not an object, specifically because it inherits primitives from Java land (though this doesn't hurt much in the kind of everyday use I put Clojure to). But also because Clojure by design doesn't even attempt to be object-oriented.

Clojure does have abstractions though. For example there's an abstraction that says "this thing can be called like a function". And then you can treat any callable-thing as a function without worrying about what it is.

In Ruby, a lot of things are Enumerable, which means you can do foo.each{} and other similar things for a lot of different types of foo. Clojure has something similar with its seq abstraction. Similarly, most Clojure data types and many Java ones are seq-able, and most built-in core functions can iterate over the guts of various things using seqs. This includes regex matches, strings, directories of files, and so on.

Ruby-style OOP brings a lot of complexity and baggage which Clojure avoids by not being OOP. For example (ignoring Java for the moment), in Clojure land you don't have to worry about a member being public/private/protected, and there are few times when you have to worry about inheritance and class hierarchies. (And there's the whole thread-safety thing.) In Clojure data is stupid and immutable, and functions are just things that take input and give output, usually side-effect free. The separation is clean and this results in programs that are very easy to reason about.

Another example of consistency: Expressions. In both Ruby and Clojure, everything has a value. Things that are "statements" in other languages are instead expressions that return something. This alone makes a lot of programs just a little bit better/easier to write.

Aesthetics

Like Ruby, Clojure code tends to be terse and expressive.

Ruby reads like poetry, because it's mostly words and not so much punctuation. In Ruby, you have none of Perl's sigils, very little of C's semi-colon line-endings and curly-delimited blocks. Especially when you start omitting optional parentheses, it has a very minimalistic vibe which is appealing to many.

    def foo(bar)
      bar.each do |x|
        puts x
      end
    end

Clojure's s-expressions are another story, of course. Some love them, some hate them. Personally, I love them. The tired old trope about Lispers not paying attention to parentheses is true; after a while they blend into the background.

    (defn foo [bar]
      (doseq [x bar]
        (prn x)))

What I see:

    defn foo [bar]
      doseq [x bar]
        prn x

Clojure has the same minimalistic feel to it, in my eyes. It also helps that so many Clojure function names are short and concise. And being able to use punctuation like ? and ! in variable/function names (true? and false? are function names), hyphens instead of underscores... these things help Clojure read smoothly.

But along with aesthetics, in Clojure you get the benefits of s-exps: no order of operations to deal with, and absolute consistency. Everything is (function param1 param2 param3). Look at how many syntax rules you have to memorize for the Ruby code above to make sense. Dot means method call, blocks are do/end delimited and have those weird pipes in there, etc.

Literals and syntax sugar

Ruby has literal syntax for many types. This includes:

  • :symbols
  • [arrays]
  • {:hash => :maps}
  • /regex/
  • do block end and {block}

This really is a big deal. I'm spoiled and I can't use a language without these things nowadays. It saves a ton of typing and it makes those things stand out in the code, making it both easier to write and to read. I use those structures all the time in every program I write, so they should have a terse representation.

Clojure has literal support for the same types as Ruby, and they even look mostly the same (with #"regex" being a change I can live with). And then it also has (among others):

  • #{sets}
  • #(function-literals %)
  • '(quoted forms)
  • `(quasi-quoted ~forms)

Is this a contradiction of my last point? What happened to s-expressions and consistency? Well, in Clojure, the reader shortcuts are just sugar that reduce to s-exps. You can avoid all use of that sugar if you hate it. (hash-map :key "val"), (vector 1 2 3), (quote foo) etc.

But more importantly, let's draw a(n arbitrary) distinction between "good" syntax and "bad" syntax.

Clojure's reader-macro sugar makes your code shorter, but doesn't change the structure of your code. Take a function call (f x y z), and you can always substitute a vector or regex literal or quoted form into it. (f [vector] #{set} #"regex"). The syntax sugar is very local, very self-contained. It doesn't leak into the surrounding code. And of course you can combine them in nearly arbitrary ways: '[quoted vector], {:hash-map-containing-a #{:set 'of #(functions)}}. This is "good" syntax.

Compare this to things like the x ? y : z construct, or heredocs. These things are not as orthogonal. They not only mean something on the "inside", they also influence and interact with the code before and after them, thanks to precedence rules and special parsing rules. Can you stick a heredoc in the middle of a function call? Maybe (I don't even know), but have fun with the indentation and line-breaks if so. When should you use do/end and when should you use {} for blocks? When do you need parens around your ternary if-then-else construct and when don't you? When do you need to use and and when &&? That's the "bad" kind of syntax. Sometimes, maybe even most of the time, it makes your code shorter, but there are a lot of rules to memorize and you never know when you'll be bitten.

Clojure largely avoids the "bad" syntax while taking advantage of the "good". Reader macros make your code shorter and visually easier to scan, but they rarely require you to do backflips to get your code to compile or run properly.

First-class functions

Ruby's blocks and yield and friends let you deal with first-class functions. This is a huge step in the Lisp direction already, and it's one of the things that makes Ruby great. But there are limits. Blocks use funky, special syntax, and in idiomatic Ruby, you will pass around only one block per method. There's the whole lambda and proc mess, and then there are methods-as-objects which are different still. And a lot of Ruby just calls .send on an object and passes in a method name as a symbol.

Being a Lisp, Clojure takes this a bit further. First-class functions are ingrained in nearly everything you do in Clojure. And they are easy to define and easy to call. Define f via defn (to make it top-level), fn (for a local function), or #() (sugar for fn), and then call it like (f).

Clojure also takes advantage of some functional-programming mainstays like partial and complement and comp(osition). We're not in full-blown Haskell territory, but it's a lot more FP than idiomatic Ruby.

And hash-maps, vectors, sets, keywords, and symbols are also callable as functions in Clojure. ({:foo 1} :foo) => 1. Many things can be treated as functions.

Metaprogramming

In Ruby you can mess with the innards of any class you want. There are facilities for defining methods dynamically, opening and inspecting classes at runtime, catch-all handlers for undefined methods, and all kinds of other dark magic. But again there are limits... Ruby needs to make use of eval to get certain things done. And monkey-patching is a shotgun aimed at your foot.

Well, if you like metaprogramming, Lisp macros are top of the line. You can abstract away boilerplate with a vengeance. Macros are the ultimate application of DRY.

Clojure doesn't deal much with classes, so there isn't much of that kind of introspection, but the Lisp principle of code-as-data enables a kind of introspection that you won't find in Ruby. The line between compile-time and run-time is very blurry, which enables all kinds of magic.

Java itself does offer Ruby-style reflection and such, if you need it, but you won't often, while in Clojure land.

Multimethods (and soon, protocols and defrecord) let you avoid monkey-patching and get some of the same kinds of "extend a class" things done in a saner and safer way.

So?

Fogus suspects:

Ruby programmers being the adventurous lot to begin with, are not satisfied with “halfway to Lisp”. Instead, they want it all.

This is true in my case. I like Ruby largely insofar as it borrowed and adapted many great features of Lisp. It only makes sense that I would like Clojure, which takes most of those things one step further. Clojure in particular, as a "modern" Lisp with vaguely Ruby-like syntax in certain places, is an obvious choice.

On top of that, Clojure is fast, thanks to the JVM. Ruby has JRuby too, but vanilla Ruby is not known for its speed. Clojure integrates with a REPL in a way that Ruby really doesn't, making interactive development enjoyable. Clojure is a compiled language, which has benefits for deployment. And again, there's the whole thread-safety thing. Clojure is awesome for writing sane, safe multi-threaded programs. These things are rather appealing.

I do still use Ruby though. Ruby is great for scripting, Clojure not so much, thanks to the JVM startup time, among other things. Ruby can be banged out quickly in any editor, but Clojure isn't much fun to edit in any editor that lacks good paren-matching support and REPL integration.

Rubygems offers dead-simple install of a ton of libraries, whereas Clojure is still working out the details of a standard build tool and install tool. Ruby has a library for anything, and while Clojure can use Java libraries, Java libraries tend to be huge and feature-rich, sometimes too huge for one-off tasks where a small Ruby library is a perfect fit.

June 09, 2010 @ 6:22 AM PDT
Cateogory: Programming
Tags: Ruby, Clojure

19 Comments

Dmitri
Quoth Dmitri on June 09, 2010 @ 9:33 AM PDT

As far as build tools go, I've had great luck with using Maven with Clojure. It's a bit ugly, but makes it trivial to manage dependencies, and Clojars is coming along quite nicely. Since Leiningen uses Maven under the covers, it has access to all the same repositories.

Brian
Quoth Brian on June 09, 2010 @ 9:52 AM PDT

Leiningen and Maven both work fine, but nowhere near Rubygems' ease-of-use.

Sankara Rameswaran
Quoth Sankara Rameswaran on June 09, 2010 @ 4:50 PM PDT

Tag this article as myth buster :). Clojure's syntax is indeed refreshing and surprisingly consistent, except for the occasional confusion with parameter order, which is mostly expected and would iron out as it evolves.

Michael Kohl
Quoth Michael Kohl on June 09, 2010 @ 7:12 PM PDT

As well written as your SO posts usually are and I agree with most points. I think in the end Fogus got exactly what he wanted, he sparked discussions around the net about the Ruby-Clojure connection, thus getting "both sides" to think about it.

Florian Gilcher
Quoth Florian Gilcher on June 09, 2010 @ 11:30 PM PDT

Nice article, but I do have some nitpicks:

You say - without further claims - that Clojure is fast because of the JVM, JRuby as well. Have you compared more recent Ruby implementations like MRI 1.9 to JRuby? How do they compare? In my experience, they tend to be faster. The JVM is nothing you throw at a language to magically turn it into a fast one.

As we are speaking about the JVM, Ruby has another pretty big advantage: if you write pure Ruby, you can use it on the JVM - or not. You may also count .NET, although IronRuby is a bit too far behind to count.

The other thing: Ruby 1.9 doesn't need eval for anything. Most of the time, eval was used to work around the fact that blocks did not accept the full argument list that methods did, making the use of #define_method a pain. Other cases may be loading files in a specific, non-standard way, but this is a "use it or not" thing.

Jason Amster
Quoth Jason Amster on June 09, 2010 @ 11:45 PM PDT

I have been trying to teach myself clojure for quite some time lately yet the I find it much harder than when I learned Ruby. What I loved about the Ruby community was the way the all push towards some really common best practices. You can look at a few good projects and learn how to structure your own... I have yet to find that nirvana in community or projects with clojure. Yes, I get the syntax, but I have yet to grasp the "This is how you build an app or library" guidance I got from just looking at other rubyist's code.

Carl Smotricz
Quoth Carl Smotricz on June 10, 2010 @ 1:09 AM PDT

2c about performance from a non-expert dabbler in Ruby and Clojure:

When I heard about Rails about 2 years ago, I built a small Web app in it. Development was quick even though I was learning Ruby and Rails alongside it, and my users were happy. But some of the calculation work in the app was running disappointingly slowly; I decided that Ruby was too slow for me to pursue on most of my projects.

I've since heard about the great work on JRuby. It seems to be faster than the reference implementation by at least a factor of 2, often more; but it's still dog slow compared with Java in server mode.

Ruby is a joy to throw code together in, and sometimes it's nice to be unconstrained by Clojure's functional-ness. But for compute-intensive code, Clojure and Java achieve roughly C performance, and run circles around Ruby, even JRuby.

Each language has its strengths, and I'm grateful for both.

Chas Emerick
Quoth Chas Emerick on June 10, 2010 @ 1:10 AM PDT

Brian: gems is easy to use (at least in part) because its scope is much smaller than maven. It's true that maven doesn't scale down to the simplest of use cases, but it's definitely true that you'll likely never reach its upper limits. That's either good or bad, depending on where you're coming from. I'm sure you've seen by now, but: Clojure Polyglot Maven

Brian
Quoth Brian on June 10, 2010 @ 1:26 AM PDT

Florian: I haven't done much with 1.9, so I'm not sure of its performance. I wouldn't doubt that it's much faster than 1.8 MRI. (Couldn't be much slower.)

James
Quoth James on June 10, 2010 @ 2:01 AM PDT

As a Ruby user which has been exploring Clojure lately I have to admit that there are similarities which is probably the reason why I find Clojure interesting.

I wish the API documentation was better organized, though - in a similar fashion to the cheatsheet (With categories for specific purposes) because in order to find anything useful in the core namespace (that is not covered in the cheatsheet) I need to browse functions one by one hoping to bump into one that fits my needs.

Perhaps once Clojure 1.2 gets released that area will get improved.

Imran Rafique
Quoth Imran Rafique on June 10, 2010 @ 3:24 AM PDT

Brian (long time lurker here),

I see that you highlight Ruby's strengths for those areas where the JVM holds clojure back (JVM startup time, and perhaps also JVM memory usage). I would be interested to hear what you think about using something closer in spirit to clojure for those non-JVM'able areas (scripting, small gui utils, etc). For example, scheme (eg: chicken or gauche scheme?). Lisp-1 to lisp-1 :)

Its a question I'm having to address myself, as I love clojure, but am finding that the JVM doesn't lend itself to many areas where I need to get something done.

Brian
Quoth Brian on June 10, 2010 @ 3:39 AM PDT

Imran: For shell scripting, there's a long tradition of Perl/bash, to the point where they're almost perfect for the job at this point. (And so is Ruby, by extension, insofar as it borrowed from Perl).

My shell scripting tends toward the data-munging side. There is a time when I want to write out *command-line-args* and enjoy the fact that it's immutable and dynamically-bindable and seq-able all that good stuff. And then there are times when I want to do ruby -nle ... and get it over with. If all I want to do is grep over a bunch of files, then Ruby's Find.find is good enough. On the other hand Java I/O is robust but so verbose that it's painful to consider for shell scripting. The kinds of shell scripts I write are run-once, so there's no need for a REPL or iterative development. If they aren't right the first time, I probably hosed my directory anyways.

For GUI work I have used Clojure, and it's just about good enough, if you can stomach Swing. Java's Qt Jambi was awesome, before the project was canned. :( I have tried Common Lisp for GUI work and it's quite painful. Never tried Scheme but I'll keep it in mind. Clojure is only good at GUIs because Java is good at them.

GUI libraries seem to be one area where you absolutely need to have people actively working to keep the libraries up-to-date and to weed out bugs, at least in the Linux world, with Qt and GTK and friends evolving so fast. GUI toolkits and toolkit-wrappers age very quickly. Java has a lot of eyes on it. Scheme, probably not so much. Same problem for Common Lisp.

But yeah, Clojure isn't the perfect tool for every job. Not by a long shot. Personally I love Clojure for web dev and little GUI apps and database work. I don't feel bad about using other languages for other jobs.

Thanks for lurking.

Brian
Quoth Brian on June 10, 2010 @ 3:45 AM PDT

James: The API docs are very terse, yes. Once you are familiar with the language that terseness is actually kind of nice, but it can be hard to understand in the beginning.

I recommend trying out some of the new Clojure books if you haven't already. Programming Clojure and the The Joy of Clojure for example. They are a more gentle way to learn the language.

Also don't forget about the docstrings you can access right from the REPL. (find-doc #"some-regex") and (doc some-function-or-var) are useful. That's how I normally look up docs for core functions. And try this:

user> (use 'clojure.repl)
nil
user> (source map)

(clojure.repl was previously called clojure.contrib.repl-utils). source lets you view the source for any Clojure function, assuming you have the sources somewhere the code can find them. Very handy.

Terax
Quoth Terax on June 10, 2010 @ 5:07 PM PDT

Nothing new. People didn't write new Lisps after Common Lisp and/or Scheme came out...

Apart from technical reasons, people like clojure a lot because it is new, so a lot of the newcomers don't feel like they have to catch up with 40 years of history. No, I'm not saying that Common Lisp would be that much harder, I'm just talking about appearances. Just like clojure seems smaller, if you conveniently ignore both the JDK and the fact that it's still young and expanding...

Apart from that, the immutability and concurrency features attract lots of experiences Lispers and people coming from other programming languages.

It's also a good escape-from-Java option, for those who have to work with legacy APIs.

Brian
Quoth Brian on June 11, 2010 @ 1:11 AM PDT

Justim: Heh, I think you're right. Fixed.

bwts
Quoth bwts on June 11, 2010 @ 3:53 AM PDT

The attraction of clojure to rubyists..

It's a taste thing. Rubyists were attracted to Ruby because of Matz' good taste. Programming in Ruby is a joy taking the best from other languages, producing something that in my view is better.

Rich Hickey also has excellent taste and has crafted a platform for the both the hard-core performance junkie and computer science nerd disappointed that Lisp never caught on as big as it should have.

ilan berci
Quoth ilan berci on August 19, 2010 @ 4:10 AM PDT

Very good article and I appreciate the time you took to write it.. I concur on the special cases surrounding OO constructs within Ruby and find the Clozure approach more cleaner by design.

For example I dislike the following: (class << self; self; end).send(:define_method, :foo) to define class level methods at run time.. (eval() amounts to pretty much the same thing)

While I understand it, and I admit to it being rare, it just seems like OO is almost getting in the way with ruby..

As for the private, protected, thingy.. that too left a bad taste in my mouth as I could simply evoke a private method anyways by .send()'ing.. so why put them in, in the first place? (I realize that private, protected, public are class level methods in Module and are not actual language constructs)

Thanks to some amazing reading material, I now understand why inheritance breaks encapsulation in the OO world, and the community now generally touts flater inheritance hierarchies. So why bother with inheritance in the first place?? The only real way to protect state is with closures or immutability.. instance variables just introduce SMP headaches..

Anyways, I still love Ruby and I apologize for not learning lisp sooner. I am very great-full to the Clozure community for opening my eyes more

Oh.. and the reason Ruby reads like poetry is not because of the lack of punctuation, it's because of it's brilliance in accomplishing so much with so little..

ilan

roger the ward choir music guy
Quoth roger the ward choir music guy on December 19, 2010 @ 9:47 PM PST

Nice to hear that there's something as elegant as ruby but that runs faster. Guess I'll have to crack open the books, as much as I love ruby...

regeya
Quoth regeya on November 15, 2011 @ 6:59 AM PST

Here I am, a year and a half later, responding to Jason Amster's post. I, too, am trying to learn Clojure, and am finding it harder than Ruby. Brian's blog has inspired me to keep plugging away, though.

Speak your Mind

You can use Markdown in your comment.
Email/URL are optional. Email is only used for Gravatar.

Preview