/jrclj

JRuby Clojure Bridge

Primary LanguageRuby

JRuby Clojure Bridge

This is an adapter library for easily executing clojure from JRuby.

Installation

This library is available to be installed via jgem:

jgem install jrclj

Note that you will also need JRuby, the clojure jar and any jar files your code depends on.

Source Code

The source code for this project is available on github. The source archive includes rspec tests and a few dependent jars, which may help you answer questions that this guide does not.

Dependencies

You must have clojure and any other dependent libraries you wish to use already present (downloaded). I thought about shipping the clojure jar with the project as a default but then decided against it since it would end up embedding a fixed version of clojure within the gem.

Once you have access to your own jar files, you will first need to require them. The library does not do this for you, it can not know what dependencies you will need. The following code will do that if you have your dependencies in a sub directory called ‘deps’:

  require 'java'
  Dir["#{File.dirname(__FILE__)}/deps/*.jar"].each do |jar|
    puts "requiring: #{jar}"
    require jar
  end

NB: you will have to require the clojure jar file.

Usage

The interface is exposed via the class JRClj, which acts like a namespace for the clojure functions. It automatically imports the clojure.core namespace, so all of its functions will be available on each instance of JRClj.

core = JRClj.new

puts core.inc 3
# prints '4'

Importing Clojure Libraries

You can import other namespaces by passing their names to the constructor:

clj = JRClj.new "clojure.contrib.str-utils"

puts clj.str_join ":", [1,2,3]
# prints '1:2:3'

You can also import a clojure namespace on an already constructed JRClj instance by calling _import:

clj = JRClj.new
clj._import "clojure.contrib.str-utils"

puts clj.str_join ":", [1,2,3]
# prints '1:2:3'

Dealing with Strangely Named Clojure Functions

As the str-utils example above showed, the library makes an attempt at mapping clojure symbol names to ruby symbol names. The str-utils example shows that it ‘folds’ hyphens to underscores, since hyphens are not legal in ruby variable or function names. There is no default translation for other characters that clojure supports in symbol names though. JRClj supports an aliasing feature to allow you to deal with these situations.

The clj-xpath library contains symbols with names that are illegal as ruby methods, eg: $x:text. Using the _alias method on JRClj you can expose these methods, or using _invoke you can call them by their name (as a string):

require 'deps/clj-xpath-1.0.6.jar'
clj_xpath = JRClj.new 'com.github.kyleburton.clj-xpath'

puts clj_xpath._invoke("$x:text", "//foo", "<foo>bar</foo>")
# prints 'bar'

clj_xpath._alias "x_txt", "$x:text"
puts clj_xpath.x_txt("//foo", "<foo>bar</foo>")
# prints 'bar'

Interactive JRuby + Clojure : IRB

Given that you have a project with a lib directory (containing Ruby code) and a deps directory (containing Java jar dependencies). Placing the following script into a jrepl file and making it executable will give you an IRB with the necessary libraries available.

#!/usr/bin/env jruby
require 'rubygems'
require 'java'
# given that your files are in ./deps (relative to the location of this file)
Dir["#{File.dirname(__FILE__)}/deps/*.jar"].each do |jar|
  require jar
end
# and you have a ./lib directory (again, realtive to this file)
$:.unshift "#{File.dirname(__FILE__)}/lib"
require 'jrclj'

require 'jruby'
JRuby.objectspace = true

require "irb"
IRB.start(__FILE__)

Using Clojure’s Eval

Clojure’s eval does not work on strings. You must first invoke Clojure’s reader to transform a string into a Clojure data strucutre which can then be evaluated.

>> clj = JRClj.new
>> clj.eval clj.read_string "(+ 3 4)"
>> s = clj.eval clj.read_stirng ":symbol"
>> s.methods - Object.methods
=> ["__jsend!", "applyTo", "applyTo__method", "apply_to", "apply_to__method", "between?", "call", "call__method", "class__method", "clone__method", "compareTo", "compareTo__method", "compare_to", "compare_to__method", "equals", "equals?", "equals__method", "equals__method?", "finalize", "finalize__method", "getClass", "getClass__method", "getName", "getName__method", "getNamespace", "getNamespace__method", "get_class", "get_class__method", "get_name", "get_name__method", "get_namespace", "get_namespace__method", "hashCode", "hashCode__method", "hash_code", "hash_code__method", "initialize", "invoke", ...
>> s.to_sring
=> ":symbol"
>> s.name
=> "symbol"
>> s.invoke clj.eval(clj.read_string "{:symbol \"value\"}")
=> "value"
>>

Complete Example

user@host ~/projects/jrclj$ ls deps/
clj-xpath-1.0.6.jar
clojure-1.0.0.jar
clojure-contrib-1.0.0.jar
xalan-2.7.1.jar

user@host ~/projects/jrclj$ cat example.rb
require 'rubygems'
require 'java'
Dir["#{File.dirname(__FILE__)}/deps/*.jar"].each do |jar|
  require jar
end
require 'jrclj'

clj = JRClj.new
puts "clj.inc: #{clj.inc 99}"

user@host ~/projects/jrclj$ jruby example.rb
clj.inc: 100