Chris Umbel

Mirah, Ruby Syntax on the JVM

Although languages like Java and C# have soured with me over the last few years I still believe their runtimes (the JVM and CLR respectively) are sound. A sizable portion of the code I write for my day job is in JRuby. We get a number of advantages from that. We can use the industrial-strength infrastructure components Java brings to the table and leverage mountains of existing, production-quality java libraries. We can also benefit from the expressiveness and dynamism of Ruby. carbungles

Still, JRuby isn't perfect. While its performance is fine for the average application or script you'd have a hell of a time writing something like, say, Lucene in JRuby as it's impractical from a performance point of view.

I suppose the lesson there is that Java is the systems language of the JVM and JRuby/Jython/Groovy are fine for abstract implementations.

There is a specific alternative to the Java language, though, that's piqued my interest lately. It was not only born from JRuby but employs JRuby in its compilation: Mirah.

Mirah is developed primarily by Charles Nutter, a principal developer of JRuby, and is an attempt applying Ruby syntax at the raw JVM. It's statically-typed, equally performant to Java and doesn't require a runtime library a la JRuby.

Basically the idea is that you could use it as a drop-in replacement for the Java language itself.

Here I'll walk you through some examples I wrote while acquainting myself with Mirah.

Example 1: Hello, World!

Ah, the canonical "Hello, World!" example. This is very Ruby-like. No class, no static main method, just good old "puts". The compiled bytecode will include a class and static main method, but Mirah takes care of that for us.

puts "hello, World!"

Example 2: Java Objects

Now this will look a little more Java-like and include familiar classes to a Java developer. The actual iteration looks far more Ruby-like, however.

import java.util.ArrayList

list = ArrayList.new([3, 9, 5])

list.each do |x|
  puts x
end

Example 3: Basic Class

Of course this is the JVM we're talking about so classes are a core component as is demonstrated here. This is also the first example that shows the strong-typing in action. The "name" parameter of the constructor is followed by ":String" indicating that it's of type java.lang.String.

class Person
  def name
    @name
  end

  def initialize(name:String)
    @name = name
  end
end

person = Person.new('Gerald McGoo')
puts person.name

Example 4: Inheritance

Inheritance follows the typical Ruby syntax.

class Person
  def name
    @name
  end

  def initialize(name:String)
    @name = name
  end
end

class Programmer < Person
  def favorite_language
    @favorite_language
  end

  def initialize(name:String, favorite_language:String)
    super(name)
    @favorite_language = favorite_language
  end
end

programmer = Programmer.new('Gerald McGoo', 'Mirah')
puts "#{programmer.name} loves #{programmer.favorite_language}"

Example 5: Swing and Blocks

This example, while demonstrating swing, illustrates a much more important point. See the ruby-block-ish implementation of the action listener closure? Now that's clean! Also note that clicked_count is not final.

This is also a good example to demonstrate the type inference of Mirah. Sure, Mirah is strongly-typed, but I'm not explicitly declaring the type of "frame". Mirah sees that I'm assigning it to a new instance of JFrame and goes with that.

import javax.swing.JFrame
import javax.swing.JButton
import javax.swing.JOptionPane

frame = JFrame.new 'Click Counter'
frame.setSize 200, 100
frame.setVisible true

button = JButton.new 'Click me'
frame.add button
clicked_count = 0

button.addActionListener do |e|
  clicked_count += 1
  JOptionPane.showMessageDialog nil, String.valueOf(clicked_count)
end

frame.show

Performance Comparison

As I stated above one of the primary intentions of Mirah was to maintain an identical performance profile to Java. I admit my attempt to benchmark it here isn't overly scientific and is rather rough but certainly illustrates the point that bytecode resultant from Mirah performs similarly to strait Java.

Also here you'll see some of the more strongly-typed characteristics of Mirah such as casting [(type)variableName in Java becomes type(variable) in Mirah].

These examples essentially perform internet checksums quite crudely on 8 byte chunks of a 10MB data file storing the results in an ArrayList. A little computation, a little IO (probably too much) and a little usage of typical Java collections.

Java

import java.io.FileInputStream;
import java.util.ArrayList;

public class Main {
    public static int inetChecksum(byte buff[]) {
        long sum = 0;
        int datum = 0;

        for(int i = 0; i < buff.length; i += 2) {
            datum = (0xffff & buff[i] << 8) | (0xff & buff[i + 1]);
            sum += datum;
        }

        while((sum >> 16) > 0)
            sum = (sum >> 16) + (sum & 0xffff);

        return ~(int)sum & 0xffff;
    }

    public static void main(String[] args) {
        byte[] data = new byte[8];
        ArrayList sums = new ArrayList();

        try {
            long start = System.currentTimeMillis();
            FileInputStream fis = new FileInputStream("test.dat");

            while(fis.read(data) > 0) {
                sums.add(new Integer(inetChecksum(data)));
            }
            
            fis.close();
            System.out.println(System.currentTimeMillis() - start);            
        } catch (Exception ex) {            
        }
    }
}

Mirah

import java.io.FileInputStream
import java.util.ArrayList

def inet_checksum(buff:byte[]):int
  sum = long(0)
  datum = 0
  i = 0

  while i < buff.length
    datum = (0xffff & buff[i] << 8) | (0xff & buff[i += 1])
    sum += datum
    i += 1
  end

  sum = (int(sum) >> 16) + (sum & 0xffff) while (int(sum) >> 16) > 0

  ~int(sum) & 0xffff
end

data = byte[8]

begin
  start = System.currentTimeMillis
  sums = ArrayList.new
  fis = FileInputStream.new 'test.dat'
  
  sums.add Integer.new(inet_checksum(data)) while fis.read(data) > 0

  fis.close
  puts System.currentTimeMillis - start
rescue
end

I ran three trials resulting in:

Java

2127 ms
2088 ms
2119 ms
AVG: 2111 ms

Mirah

2231 ms
2031 ms
2043 ms
AVG: 2101 ms

Let's just call that about even.

Doesn't Haves

Thus far I've yammered on about what Mirah does. Now here's some notes on what Mirah doesn't do.

  • Generics - At this point Mirah doesn't do generics like Java (as of version 5 I believe). I spoke with Charles Nutter about this and he believes it'll likely be included in the future, however.
  • Ranges and other Ruby goodness - Mirah isn't Ruby so a few facilities a Ruby developer might be used to are missing such as ranges. In other words the following Ruby code isn't valid Mirah: (0..10).step(2) {|i| puts i}

Final Thoughts

Like I mentioned in the introduction I see great value in the modern runtimes but am not particularly thrilled with the typical languages used to program them. I'll admit that a lot of that has to do with just my personal taste but I truly believe languages like Mirah offer some additional conciseness and features that enhance productivity.

I, for one, and excited about it.

Tue Mar 29 2011 02:00:52 GMT+0000 (UTC)

Follow Chris
RSS Feed
Twitter
Facebook
CodePlex
github
LinkedIn
Google