12
Sep
09

Varargs in Scala


Eine Methode, die eine variable Anzahl Argumente gleichen Typs (sogenannte “Varargs”) aufnehmen kann, ist eine praktische Sache. Sowohl Java und Scala haben dieses Feature, allerdings mit ein paar kleinen Unterschieden. Gemeinsam ist in beiden Sprachen die Regel, dass bei einer Methode nur das letze Argument ein Vararg-Parameter sein kann. Aber während Java die Varargs als Arrays repräsentiert, sind es in Scala Sequenzen. Seq ist ein wichtiges Collection-Trait, das z.B. von List, Set, Map und Array implementiert wird.

In Scala zeigt ein Sternchen ähnlich wie bei Grammatiken (wie EBNF) oder regulären Ausdrücken an, dass null bis unendlich viele Elemente vorkommen können:

def varargTest(ints:Int*) { ints.foreach(println) }
//--> varargTest: (Int*)Unit

Innerhalb der Methode ist der Vararg-Parameter ints eine Sequenz, das heißt man kann darüber itererieren und viele Collections-Methoden wie foreach, map, filter, find u.s.w. verwenden.

Ein kleiner Test:

varargTest() 
//keine Ausgabe
varargTest(1,2,3)
//--> 1
//--> 2
//--> 3

Was ist aber, wenn wir die Parameterwerte schon “gepackt” als Sequenz, etwa als Liste, vorliegen haben? So gehts jedenfalls nicht:

varargTest(List(1,2,3,4,5))
//--> error: type mismatch;
//-->  found   : List[Int]
//-->  required: Int
//-->        varargTest(List(1,2,3,4,5))
//-->                   ^

Und das ist auch gut so. In Java gibt es nämlich in einigen Fällen durch die vermeintlich bequeme Schreibweise Zweideutigkeiten: Was ist, wenn ich in Java Varargs vom Typ Object vereinbare und dann ein Array übergebe: Ist es der erste Vararg-Parameter, oder beinhaltet es die Vararg-Parameter? Oder was ist, wenn ich parallel zur Vararg-Version eine Methode mit einem entsprechenden Array definiere?

Wie man sieht, ist es sinnvoll, Vararg-Aufrufe mit Sequenzen speziell zu kennzeichnen, damit solche Verwirrungen vermieden werden. Scala imitiert dabei beim Arufruf die Stern-Syntax der Definition:

varargTest(List(1,2,3):_*)
//--> 1
//--> 2
//--> 3

Dabei muss allerdings der Unterstrich statt dem Typ verwendet werden.

Wenn man eine Vararg-Methode häufig verwendet, und auch keine der oben genannten Zweideutigkeiten drohen, wäre es natürlich schön, wenn man dem Aufrufer diese recht spezielle Syntax ersparen könnte. Das geht ganz einfach, wenn man in einer gleichnamigen Methode List oder irgendeine andere passende Unterklasse von Seq als Argumenttyp verwendet und den Aufruf an die ursprüngliche Vararg-Methode delegiert:

def varargTest(ints:List[Int]) { varargTest(ints: _*) }
varargTest(List(1,2,3))
//--> 1
//--> 2
//--> 3

Allerdings kann man leider nicht Seq selbst verwenden, denn solche Versuche quittiert der Compiler folgendermaßen:

//--> error: double definition:
//--> method varargTest:(ints: Seq[Int])Unit and
//--> method varargTest:(ints: Int*)Unit at line 10
//--> have same type after erasure: (ints: Sequence)Unit
//-->   def varargTest(ints:Seq[Int]) { varargTest(ints: _*) }

Bleibt noch die Frage nach der Java-Kompatibilität zu klären. Und die ist so lala…

System.out.printf("%d %s %n", Integer.valueOf(1), "x")
//--> 1 x 

Die primitiven Typen machen hier (wie auch im richtigen Leben) Probleme: Da die Varargs als Object definiert sind, und sowohl eine Umwandlung nach java.lang.Integer als auch nach scala.runtime.RichInt möglich wäre, um aus 1 ein Objekt zu machen, streikt der Compiler bei der Umwandlung und überläßt uns die ganze Arbeit :-(

Kann man auch ein Array als “gepackte” Version nach Java übergeben? Ja, aber so wohl ist dem Compiler dabei anscheinend nicht, denn er hält uns gleich eine Gardinenpredigt:

System.out.printf("%d %s %n", Array[AnyRef](Integer.valueOf(1), "x"))
//--> warning: I'm seeing an array passed into a Java vararg.
//--> I assume that the elements of this array should be passed as individual arguments to the vararg.
//--> Therefore I follow the array with a `: _*', to mark it as a vararg argument.
//--> If that's not what you want, compile this file with option -Xno-varargs-conversion.
//-->        System.out.printf("%d %s %n", Array[AnyRef](Integer.valueOf(1), "x"))
//-->                                      ^
//--> 1 x 

Na gut, dann machen wir mal, was uns der Compiler sagt, dann gibt er auch Ruhe:

System.out.printf("%d %s %n", Array[AnyRef](Integer.valueOf(1), "x"):_*)
//--> 1 x 

Die Java-Kompatibilität ist also gegeben, wenn auch mit ein paar kleineren Ecken und Kanten. Es gibt bestimmt Wichtigeres zu tun, als diese gerade jetzt auszubügeln, aber ich denke schon, dass sich da noch etwas tun wird, wenn sich der Scala 2.8-Staub ein wenig gelegt hat. Apropos Scala 2.8: Hinsichtlich der Einführung von Default- und benannten Argumenten könnte ich mir durchaus vorstellen, dass da ein paar interessante Grenzfälle (a.k.a. “Bugs”) im Zusammenhang mit Varargs auftreten, an die noch keiner gedacht hat. Mal sehen, wie gut sich diese Features untereinander vertragen.

So, viel mehr fällt mir zu Varargs nicht ein. Und so wie auch die variabelste Argumentliste einmal ein Ende finden muss, finde auch ich eins, und zwar genau hier.

About these ads

3 Responses to “Varargs in Scala”


  1. 1 Stefan Endrullis
    17. September 2009 um 10:31

    Danke für den Beitrag, aber einen Punkt muss man hier doch noch richtig stellen, denn leider ist gerade im Bereich der Varargs die Java-Kompatibilität nicht vollständig gegeben. Wirklich kompliziert wird es nämlich, wenn man von Java aus eine Scala-Methode mit Varargs aufrufen möchte. Beispiel siehe https://lampsvn.epfl.ch/trac/scala/ticket/2220
    Hier lässt sich das Problem nicht einfach mit einem kleinen “_:*”-Cast lösen, was bedeutet, dass man sich sein Seq-Object irgendwie (ich weiß auch nicht wie) in Java zusammenbauen müsste, um es anschließend zu übergeben. Für meine Begriffe ist das eine sehr unschöne Angelegenheit, die mich am Einsatz von Scala bereits hat zweifeln lassen. Ich hoffe nur, dass das Problem in Version 2.8 behoben wird.


Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ photo

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s


Folgen

Erhalte jeden neuen Beitrag in deinen Posteingang.

%d Bloggern gefällt das: