... bytes and babes of JRuby

Better Closures for (Functional) Java

One of the neat features of JRuby’s Java Integration is the ability to dynamically implement interfaces using a block a.k.a. the neat proc-to-interface conversion.

module CalculatorMachine
  def self.calculate(n); (1..n).reduce(1, '*') end

java.util.concurrent.ForkJoinPool.class_eval do
  # help matching due overload : submit(java.lang.Runnable)
  java_alias :exec, :submit, [java.util.concurrent.Callable]

pool = java.util.concurrent.ForkJoinPool.common_pool
pool.exec do # submit(java.util.concurrent.Callable)
  pool.exec do # spawn a sub-task from a task
    res = CalculatorMachine.calculate(1000)
    puts "sub1 calculated: 1000! ~ 10^#{res.to_s.size}"
  pool.execute do # another execute(java.lang.Runnable)
    res = CalculatorMachine.calculate(10_000)
    puts "sub2 calculated: 10_000! ~ 10^#{res.to_s.size}"
  res = CalculatorMachine.calculate(100)
  puts "task calculated: 100! ~ 10^#{res.to_s.size}"

res = CalculatorMachine.calculate(100_000)
puts "main calculated: 100_000! ~ 10^#{res.to_s.size}"

No need to have a class (or 2) ‘including’ the java.util.concurrent.Callable for the submit method, since JRuby passes the block as an implementation of the (last) interface argument.

Great way to ease scripting over Java APIs that has been available since ancient JRuby times. However, there’s been some collisions notably around on Java 8’s novel stream support where Ruby block semantics otherwise match up very well with functional interfaces.

The problem is that proc-to-interface dispatch logic is using method_missing, thus a potential naming conflict (either from a core extending gem or built-in method) fails to execute the intended logic e.g. a block-based implementation for Predicate#test ends up calling Ruby’s Kernel#test instead of the actual body (the bellow example would fail).

numbers = java.util.Arrays.asList 1, 2, 3, 4, 5, 6, 7, 8

supplier = -> { } # Supplier<R>
accumulator = -> (c, e) { c << e } # BiConsumer<R,? super T>
combiner = -> (c1, c2) { c1.addAll(c2) } # BiConsumer<R,R>

three_even_squares =
  filter( ->(n) { puts "filtering #{n}"; n % 2 == 0 } ).
  map( ->(n) { puts "  mapping #{n}"; n * n } ).
  limit(3).collect(supplier, accumulator, combiner).
  to_s # an ArrayList of "[ 4, 16, 36 ]"

This has been improved in JRuby 9.1 to always bind concrete (implemented) interface methods, besides method_missing, to make sure the intended block body is executed regardless of the inherited runtime state. Looks and feels just like Java these days.

Make sure you have JRuby >= 9.1 when scripting functional Java, like above.