Scala 2.12 ma wyjść jakoś już zaraz niedługo niebawem i jak tu nie lepiej posprawdzać co nas czeka jak nie pokompilować sobie próbek kodu. Tak jak na laborkach w technikum. Tyle, iż tam sprawozdanie było od razu gotowe, wnioski spisane i w zasadzie mierzyło się do czasu aż wykres namalował się zgodnie z oczekiwaniem. A tak to w zasadzie podobnie.
Sprawdzimy faktycznie co się dzieje kompilując sobie scalę 2.12-M5 , którą to wersję można sobie ściągnąć tutaj --> http://www.scala-lang.org/download/2.12.0-M5.html oraz javap i innych takich narzędzi do roz-kompilowywania kodu.
Świat po kompilacji - Prosta Klasa z Funkcją
Według zapowiedzi kod ma być kompilowany do BAJTkodu javy 8 czyli funkcje już nie powinny zamieniać się w klasy anonimowe.
Mamy taki prosty kod w Scali
class Lib{ val fun:Int=>Int=_+1 }
Scala 2.11
Po kompilacji mamy takie oto wygenerowane pliki.Uwagę prawdopodobnie przyciąga to takie długie coś - o tym za chwilkę
ls Lib$$anonfun$1.class Lib.class lib.scalaNajpierw : Lib.class - tutaj w sumie nic nadzwyczajnego.
javap -private Lib.class Compiled from "lib.scala" public class Lib { private final scala.Function1<java.lang.Object, java.lang.Object> fun; public scala.Function1<java.lang.Object, java.lang.Object> fun(); public Lib(); }
Scala 2.11 kompiluje się do bytecodu Javy 6 a jako, iż w Javie 6 lambd nie ma to mamy dodatkowa tę oto wcześniej wspomnianą dziwną klasę
Lib$$anonfun$1.classjavap Lib\$\$anonfun\$1.class Compiled from "lib.scala" public final class Lib$$anonfun$1 extends scala.runtime.AbstractFunction1$mcII$sp implements scala.Serializable { public static final long serialVersionUID; public final int apply(int); public int apply$mcII$sp(int); public final java.lang.Object apply(java.lang.Object); public Lib$$anonfun$1(Lib); }
Przykład jest prosty i dostaliśmy tylko jedną anonimową klasę ale jak polecimy z nim trochę dalej i trochę mocniej
class Lib{ val fun:Int=>Int=_+1 val fun2:Int=>Int=_+1 val fun3:Int=>Int=_+1 val fun4:Int=>Int=_+1 val fun5:String => BigDecimal = s => BigDecimal(s) }
To generuje się tego wincyj i wincyj
ls Lib$$anonfun$1.class Lib$$anonfun$2.class Lib$$anonfun$3.class Lib$$anonfun$4.class Lib$$anonfun$5.class Lib.class lib.scala
A klasa bohaterka wygląda ostatecznie tak :
public class Lib { private final scala.Function1<java.lang.Object, java.lang.Object> fun; private final scala.Function1<java.lang.Object, java.lang.Object> fun2; private final scala.Function1<java.lang.Object, java.lang.Object> fun3; private final scala.Function1<java.lang.Object, java.lang.Object> fun4; private final scala.Function1<java.lang.String, scala.math.BigDecimal> fun5; public scala.Function1<java.lang.Object, java.lang.Object> fun(); public scala.Function1<java.lang.Object, java.lang.Object> fun2(); public scala.Function1<java.lang.Object, java.lang.Object> fun3(); public scala.Function1<java.lang.Object, java.lang.Object> fun4(); public scala.Function1<java.lang.String, scala.math.BigDecimal> fun5(); public Lib(); }
No dobra ale to już pomału odchodzi w przeszłość bo oto (pamparampampam):
Scala 2.12
Klas anonimowych w 2.12 nima :
ls Lib.class lib.scala
Klasa z funkcjami wygląda podobnie do 2.11
public class Lib { public scala.Function1<java.lang.Object, java.lang.Object> fun(); public scala.Function1<java.lang.Object, java.lang.Object> fun2(); public scala.Function1<java.lang.Object, java.lang.Object> fun3(); public scala.Function1<java.lang.Object, java.lang.Object> fun4(); public scala.Function1<java.lang.String, scala.math.BigDecimal> fun5(); public static final int $anonfun$fun$1(int); public static final int $anonfun$fun2$1(int); public static final int $anonfun$fun3$1(int); public static final int $anonfun$fun4$1(int); public static final scala.math.BigDecimal $anonfun$fun5$1(java.lang.String); public Lib(); }
I to wszystko! Jak to możliwe zapytacie!?! Ano
javap -c Lib.class public Lib(); Code: 0: aload_0 1: invokespecial #67 // Method java/lang/Object."":()V 4: aload_0 5: invokedynamic #87, 0 // InvokeDynamic #0:apply$mcII$sp:()Lscala/runtime/java8/JFunction1$mcII$sp; 10: putfield #24 // Field fun:Lscala/Function1; 13: aload_0 14: invokedynamic #91, 0 // InvokeDynamic #1:apply$mcII$sp:()Lscala/runtime/java8/JFunction1$mcII$sp; 19: putfield #28 // Field fun2:Lscala/Function1; 22: aload_0 23: invokedynamic #95, 0 // InvokeDynamic #2:apply$mcII$sp:()Lscala/runtime/java8/JFunction1$mcII$sp; 28: putfield #30 // Field fun3:Lscala/Function1;
Ten byte kod należy czytać "costam costam InvokeDynamic costam costam" czyli oto lambdy z java 8 właśnie widzimy. No czyli jest ładnie i działa i nie ma sensu się dalej rozpisywać bo jest ładnie i działa.
Traity i interfejsy
Od Javy 8 interfejsy mogą mieć metody statyczne oraz domyślne implementacje zwykłych metod. W ciekawym artykule o Scali 2.12 http://www.scala-lang.org/blog/2016/07/08/trait-method-performance.html możemy wyczytać, iż :
Kod wyjściowy w Scali
trait A{ def m() } trait B{ def m() def m2(arg:Int) :Int = arg+20 } trait C{ val field=15 def m() def m2(arg:Int) :Int = arg+field }
Scala 2.11
Poza traitem A, który ma tylko deklaracje metody - resztę traitów nie da się przedstawić przy pomocy zwykłego interfejsu z Javy6 dlatego też generowane są dodatkowe klasy z implementacjami metod
ls A.class B.class B$class.class C.class C$class.class lib.scala
Scala 2.12
Tutaj wygenerowanych plików jest mniej właśnie dzięki nowym patentom w interfejsie Javy8
ls A.class B.class C.class lib.scala
I choćby to C z definicją wartości w polu traitu da się przedstawić jako interfejs w Javie8
javap C.class Compiled from "lib.scala" public interface C { public abstract void C$_setter_$field_$eq(int); public abstract int field(); public abstract void m(); public static int m2$(C, int); public int m2(int); public static void $init$(C); }
Wołanie javy 8
Scala Lambdy Jako Java Lambdy
wywołajmy sobie Streamy z Javy8
object Main extends App{ java.util.Arrays.asList(1,2,3,4,5).stream().map(e=>e+1).forEach(i=>println(s"INFO : $i")) }
I wynik
scala2.12 lib.scala INFO : 2 INFO : 3 INFO : 4 INFO : 5 INFO : 6
Czyli poooszło ładnie!
Funkcje Scali zamiast Lambd
object Main extends App{ val increment:Int=>Int=e=>e+1 val display: Int=>Unit = i=>println(s"INFO : $i") java.util.Arrays.asList(1,2,3,4,5).stream().map(increment).forEach(display) }
To niestety nie zadziałało. Być może trzeba jakieś konwersje zaimportować.
scala2.12 lib.scala /home/pawel/projects/scalaeksperymenty/212/212/lib.scala:14: error: type mismatch; found : Int => Int required: java.util.function.Function[_ >: Int, _] java.util.Arrays.asList(1,2,3,4,5).stream().map(increment).forEach(display) ^ one error found
Także ogólnie jeżeli nie będzie żadnego globalnego kataklizmu to przyszłość programowania zapowiada się wspaniale.