Was macht `: _ *` (Doppelpunkt-Unterstrich) in Scala?

195

Ich habe den folgenden Code aus dieser Frage :

def addChild(n: Node, newChild: Node) = n match {
  case Elem(prefix, label, attribs, scope, child @ _*) => Elem(prefix, label, attribs, scope, child ++ newChild : _*)
  case _ => error("Can only add children to elements!")
}

Alles darin ist ziemlich klar, außer diesem Stück: child ++ newChild : _*

Was tut es?

Ich verstehe, dass es Seq[Node]mit einem anderen verkettet ist Node, und dann? Was macht : _*das

Amorfis
quelle
70
Vielen Dank, dass Sie dem Titel (Doppelpunkt-Unterstrich) hinzugefügt haben!
Gal

Antworten:

151

Es "splats" 1 die Sequenz.

Schauen Sie sich die Konstruktorsignatur an

new Elem(prefix: String, label: String, attributes: MetaData, scope: NamespaceBinding,
         child: Node*)

das heißt als

new Elem(prefix, label, attributes, scope,
         child1, child2, ... childN)

aber hier gibt es nur eine Folge, nicht child1, child2usw. so dies die Ergebnisfolge ermöglicht als die Eingabe an den Konstruktor verwendet werden.

Viel Spaß beim Codieren.


1 Dies hat keinen Cutesy-Namen im SLS, aber hier sind die Details. Das Wichtigste ist, dass es ändert, wie Scala die Argumente mit wiederholten Parametern (wie Node*oben angegeben) an die Methode bindet .

Die _*Typanmerkung wird in „4.6.2 Wiederholte Parametern“ des SLS bedeckt.

Der letzte Wertparameter eines Parameterabschnitts kann mit "*" versehen werden, z. B. (..., x: T *). Der Typ eines solchen wiederholten Parameters innerhalb der Methode ist dann der Sequenztyp scala.Seq [T]. Methoden mit wiederholten Parametern T * nehmen eine variable Anzahl von Argumenten vom Typ T an. Das heißt, wenn eine Methode m vom Typ (p1: T1, ..., pn: Tn, ps: S *) U auf Argumente (e1, ..., ek) angewendet wird, wobei k> = n ist, dann ist m in dieser Anwendung genommen, um Typ (p1: T1, ..., pn: Tn, ps: S, ..., ps0S) U zu haben, mit k ¡n Vorkommen vom Typ S, bei denen alle Parameternamen jenseits von ps frisch sind.Die einzige Ausnahme von dieser Regel besteht darin, dass das letzte Argument über eine Annotation vom Typ _ * als Sequenzargument markiert ist. Wenn m oben auf Argumente (e1, ..., en, e0: _ *) angewendet wird, wird angenommen, dass der Typ von m in dieser Anwendung (p1: T1, ..., pn: Tn, ps: scala) ist .Seq [S])

Roman Kagan
quelle
5
Wir nennen es gerne den "Smooch-Operator", obwohl es eigentlich kein Operator ist :)
Henrik Gustafsson
1
In Python heißt das Auspacken
Joshlk
Gibt es eine Begrenzung für die Länge der Sequenz, wie dies bei Java-Varargs der Fall ist?
qwwqwwq
95
  • child ++ newChild - Reihenfolge
  • : - Typzuweisung, ein Hinweis, der dem Compiler hilft zu verstehen, welchen Typ dieser Ausdruck hat
  • _* - Platzhalter, der einen beliebigen Wert + vararg-Operator akzeptiert

child ++ newChild : _* erweitert Seq[Node] auf Node*(teilt dem Compiler mit, dass wir eher mit einem varargs als mit einer Sequenz arbeiten). Besonders nützlich für Methoden, die nur Varargs akzeptieren können.

Vasil Remeniuk
quelle
1
Könnten Sie mehr über "Typzuschreibung" schreiben? Was ist das und wie funktioniert es?
Amorfis
24

Die obige Antwort sieht gut aus, benötigt aber nur ein Beispiel, um dies zu erklären. Hier ist es :

val x : Seq[Seq[Int]] = Seq(Seq(1),Seq(2))

def f(arg: Seq[Any]*) : Int = {
 arg.length
}
f(x) //1 as x is taken as single arg
f(x:_*)  // 2 as x is "unpacked" as a Seq[Any]*

Jetzt wissen wir also, was :_*Sie dem Compiler mitteilen müssen: Bitte entpacken Sie dieses Argument und binden Sie diese Elemente im Funktionsaufruf an den Parameter vararg, anstatt das x als einzelnes Argument zu verwenden.

:_*Kurz gesagt, das heißt, Mehrdeutigkeiten zu beseitigen, wenn Argumente an vararg-Parameter übergeben werden.

Keith
quelle
5

Für einige der faulen Leute wie mich konvertiert es einfach eine Seq in varArgs!

mani_nz
quelle