Element 84 Logo

Ruby Clojure Integration

05.27.2012

Lately I’ve been looking into the Clojure programming language, a Lisp that’s implemented on top of the JVM. One of the aspects of Clojure that I’m interested in is its ability to integrate into existing projects, especially Ruby projects. Since Clojure runs on the JVM and already integrates with Java it should be easy to integrate into a Ruby project run on JRuby. I discovered the JRuby Clojure Bridge gem, called jrclj, that does just that.

Demonstration

I’ve created a test project that demonstrates using jrclj. It’s on github. Follow the directions in the README to try it out. It consists of a small Clojure project, powers_of_two, and a Ruby file that demonstrates calling the powers_of_two code.

The Clojure Code

The main source code for powers_of_two is in one source file, core.clj

(ns powers_of_two.core)

(defn powers_of_two
([]
(concat [1 2 4] (powers_of_two 4N)))
([last_value]
(let [next_value (* last_value 2N)]
(lazy-seq (cons next_value (powers_of_two next_value))))))

(defn powers_of_two_improved []
(iterate (fn [v] (* v 2N)) 1))

core.clj contains two functions that do the same thing, return a sequence of numbers containing the powers of two. ie. 2^0, 2^1, 2^2, 2^3,… or 1, 2, 4, 8, … The sequences are recursively infinite but use lazy-seq to avoid recursing forever. Lazy sequences in Clojure give you the power of defining a sequence of things (numbers, database records, dates, etc) recursively and infinitely. The first powers_of_two function does it manually using recursion and the lazy-seq function. The powers_of_two_improved function uses the iterate function, which lets you build a lazy sequence from a function and a starting value. The upper case N in the examples after numbers changes them to Clojure big integers. The powers of two sequence will quickly grow beyond what a normal integer can represent.

The Ruby Code

The use_powers_of_two.rb script demonstrates calling Clojure from JRuby

require 'rubygems'
require 'java'

# Require the clojure jars
require 'powers_of_two/lib/clojure-1.4.0.jar'
require 'powers_of_two/lib/clojure-contrib-1.2.0.jar'

# Require the project jar that we want to use
require 'powers_of_two/powers_of_two-1.0.0-SNAPSHOT.jar'

# Require the jrclj gem that lets us interact with clojure code
require 'jrclj'

# Create a JRClj context. We can then import clojure namespaces which will make the functions available
clj = JRClj.new
clj._import "powers_of_two.core"

puts "2**500 == " + clj.nth(clj.powers_of_two, 500).to_s

The last line actually makes the call into Clojure. It calls the Clojure function nth which will take the 500th item from the sequence returned by the powers_of_two function. This prints out 2^500.

Repl

You can try out a repl with jruby clojure integration using the jrepl script included in the project. Run jrepl from a command line and then try the following bits of code

./jrepl
Your .irbrc has loaded
>> clj.powers_of_two
=> #<Java::ClojureLang::LazySeq:0x1558473e>
powers_of_two just returns a lazy sequence

>> clj.take( 10, clj.powers_of_two).map(&:to_i)
=> [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]

This takes the first 10 items from the Clojure sequence. We then use Ruby’s map method to convert all of the results to integers. The Ruby map method works on lazy sequences from Clojure because one of it’s interfaces is standard Java interface java.util.List. JRuby allows java.util.Lists to be treated like Ruby Arrays. This means the Clojure sequences themselves can be accessed like a Ruby array.

>> clj.powers_of_two.map(&:to_i)
NativeException: java.lang.OutOfMemoryError: Java heap space

The Clojure sequences are infinite. If you’re not careful you can run out of memory.

Why?

So why would you want to integrate Clojure with Ruby? It would certainly be simpler to limit your projects to a single language. Clojure has benefits that are worth exploring. Clojure makes it very easy to write concurrent code that perform well. Functions that don’t manipulate state and minimize or have no side effects are easier to understand and test. Macros (http://clojure.org/macros) are like metaprogramming on steroids. There are also libraries built in Clojure that would be useful to integrate into a Ruby project. An example of this is Incanter, a platform for statistical computing and graphics. The fact that Clojure integrates so easily with JRuby makes it easy to experiment.

**Jason will be speaking on mixing Clojure with Ruby at the June12 B’more on Rails meeting.

Jason Gilman

Principal Software Engineer