martedì 24 gennaio 2012

Interoperabilità tra Scala e Java

Scala è interoperabile con Java, poiché entrambi i linguaggi producono Java bytecode. Questo significa che tutte le seguenti frasi sono vere:
  • Codice Scala può essere eseguito da una Java Virtual Machine
  • Codice Java può essere "visto" da codice Scala
  • Codice Scala può esserre "visto" da codice Java
Supponiamo di avere ad esempio la seguente classe di business in Scala, una generica struttura che rappresenta l'astrazione di un numero razionale. E' costruita passando numeratore e denominatore, ad esempio Rational(2,3) è due terzi (2/3).
 package net.alessiosaltarin.rationals  
   
 class Rational(n: Int, d: Int) {  
   require(d != 0)  
   
   private val g = this.gcd(n.abs, d.abs)  
   val numer: Int = (n / g)  
   val denom: Int = (d / g)  
   println("Created " + this.toString())  
   
   def this(n: Int) = this(n, 1)  
   
   override def toString = this.numer + "/" + this.denom  
   
   def +(that: Rational): Rational =  
     new Rational(  
       this.numer * that.denom + that.numer * this.denom,  
       this.denom * that.denom)  
   
   def *(that: Rational): Rational =  
     new Rational(this.numer * that.numer, this.denom * that.denom)  
   
   private def gcd(a: Int, b: Int): Int =  
     if (b == 0) a else gcd(b, a % b)  
 }  
   
 object RationalComputer {  
   
   def performSum(r1: Rational, r2: Rational): String =  
     (r1 + r2).toString()  
   
   def performMultiply(r1: Rational, r2: Rational): String =  
     (r1 * r2).toString()  
   
 }  
   
 object RationalFactory {  
   
   def create(rationalStr: String): Rational =  
     {  
       val indexOfSlash = rationalStr indexOf '/'  
       val n = nrParse(rationalStr.substring(0, indexOfSlash))  
       val d = nrParse(rationalStr.substring(indexOfSlash + 1))  
       new Rational(n, d)  
     }  
   
   private def nrParse(nstr: String): Integer = Integer.parseInt(nstr)  
 }  
Se vogliamo offrire a questo codice una user interface, che non sia Web, abbiamo ben poche possibilità, se vogliamo rimanere nell'ambito di Scala, e cioè quelle di usare il wrapping delle librerie Swing scritto in Scala, vale a dire: http://www.scala-lang.org/api/current/scala/swing/package.html Il problema di questo approccio è che alla data di questo post manca totalmente un editor visuale che generi in output un codice Scala. Quello che abbiamo, invece, sono degli editor visuali che generano codice Swing in Java, ad esempio:
  • Netbeans Matisse
  • Eclipse Visual Editor
La buona notizia è che, stanti le premesse di cui sopra, codice Scala può essere visto da Java come se fosse una "libreria" esterna (e viceversa, tra l'altro). Possiamo infatti pensare di realizzare un'interfaccia di questo tipo:
attraverso l'editor visuale che preferiamo, generare il codice Java equivalente, e poi eseguirlo. Per farlo possiamo seguire due approcci, entrambi validi: eseguire dalla macchina virtuale Scala il codice Scala e il codice Java interpretarlo come bytecode esterno, oppure eseguire da una qualsiasi macchina virtuale Java il codice dell'interfaccia grafica e da questo richiamare il bytecode compilato da Scala come una libreria esterna. Chiaramente, è il secondo approccio quello più interessante. Infatti nella pratica avremo a disposizione macchine virtuali Java, ottimizzate a seconda dell'uso. Perché questo approccio sia percorribile, occorre costruirsi un proxy Java in grado di richiamare il codice di business in Scala. Il proxy conterrà i metodi richiamati direttamente dall'interfaccia - nell'esempio, il pulsante di 'esegui operazione'. Ad esempio:
 package net.alessiosaltarin.javaproxy;  
   
 import net.alessiosaltarin.rationals.Rational;  
 import net.alessiosaltarin.rationals.RationalFactory;  
 import net.alessiosaltarin.rationals.RationalComputer;  
   
 public class ProxyLogic  
 {  
   public static String performOperation(Operation op,   
                           String rationalOne,   
                           String rationalTwo)  
   {  
     Rational r1 = RationalFactory.create(rationalOne);  
     Rational r2 = RationalFactory.create(rationalTwo);  
     String result;  
       
     switch (op)  
     {  
          case ADD:  
          default:  
               result = RationalComputer.performSum(r1, r2);  
               break;  
                 
          case SUBTRACT:  
               throw new UnsupportedOperationException();  
                 
          case MULTIPLY:  
               result = RationalComputer.performMultiply(r1, r2);  
               break;  
                 
          case DIVIDE:  
               throw new UnsupportedOperationException();  
     }  
       
     return result;  
   }    
 }  
Il codice sopra richiama il codice Scala - il namespace
net.alessiosaltarin.rationals.Rational
Come fa? Semplicemente lo trova nel percorso del codice compilato come Java bytecode, a patto di avere la libreria Scala
scala-library.jar
nel classpath corrente. Supponendo che la classe RationalGUI sia quella generata dal tool visuale, il codice eterogeneo Scala/Java verrà eseguito dalla JVM in questo modo:
java -cp scala-library.jar;[jre,...] net.alessiosaltarin.javaproxy.RationalGUI
Utilizzando ad esempio Eclipse, è possibile aprire due progetti, uno in Scala e uno in Java, e in quello Java che contiene il metodo main, referenziare come libreria esterna il codice Scala custom e la libreria scala-library.jar.