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'
IRB
Interactive JRuby + Clojure :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