Scala 2.12 - laborki

pawelwlodarski.blogspot.com 8 lat temu

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.scala

Najpierw : 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.class
javap 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ż :

"2.12.0-M5: trait method bodies are emitted in static methods in the interface classfile. The default methods forward to the static methods."

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.

Idź do oryginalnego materiału