Que fait `: _ *` (étoile de soulignement du colon) dans Scala?

200

J'ai le morceau de code suivant de cette question :

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!")
}

Tout y est assez clair, sauf cette pièce: child ++ newChild : _*

Qu'est ce que ça fait?

Je comprends qu'il est Seq[Node]concaténé avec un autre Node, et puis? Que fait : _*-on?

amorfis
la source
71
Merci beaucoup d'avoir ajouté (étoile de soulignement deux points) au titre!
Gal

Réponses:

155

Il "splats" 1 la séquence.

Regardez la signature du constructeur

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

qui est appelé comme

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

mais ici il n'y a qu'une séquence, non child1, child2etc. donc cela permet à la séquence de résultat à utiliser comme entrée au constructeur.

Bon codage.


1 Cela n'a pas de nom mignon dans le SLS, mais voici les détails. La chose importante à obtenir est que cela change la façon dont Scala lie les arguments à la méthode avec des paramètres répétés (comme indiqué Node*ci-dessus).

L' _*annotation de type est traitée dans "4.6.2 Paramètres répétés" du SLS.

Le dernier paramètre de valeur d'une section de paramètres peut être suffixé par «*», par exemple (..., x: T *). Le type d'un tel paramètre répété à l'intérieur de la méthode est alors le type de séquence scala.Seq [T]. Les méthodes avec des paramètres répétés T * prennent un nombre variable d'arguments de type T. Autrement dit, si une méthode m de type (p1: T1,..., Pn: Tn, ps: S *) U est appliquée aux arguments (e1,..., Ek) où k> = n, alors m est pris dans cette application pour avoir le type (p1: T1,..., pn: Tn, ps: S,..., ps0S) U, avec k ¡n occurrences de type S où tout nom de paramètre au-delà de ps est frais.La seule exception à cette règle est si le dernier argument est marqué comme argument de séquence via une annotation de type _ *. Si m ci-dessus est appliqué aux arguments (e1,..., En, e0: _ *), alors le type de m dans cette application est considéré comme (p1: T1,..., Pn: Tn, ps: scala .Seq [S])

Roman Kagan
la source
5
Nous aimons l'appeler "l'opérateur Smooch", même si ce n'est pas réellement un opérateur :)
Henrik Gustafsson
1
En Python, cela s'appelle le déballage
joshlk
Y a-t-il une limite à la durée de la séquence, comme c'est le cas avec les varargs Java?
qwwqwwq
95
  • child ++ newChild - séquence
  • : - attribution de type, un indice qui aide le compilateur à comprendre, quel type cette expression a-t-elle
  • _* - espace réservé acceptant n'importe quelle valeur + opérateur vararg

child ++ newChild : _*se développe Seq[Node]en Node*(indique au compilateur que nous travaillons plutôt avec un varargs qu'avec une séquence). Particulièrement utile pour les méthodes qui ne peuvent accepter que des varargs.

Vasil Remeniuk
la source
1
Pourriez-vous en dire plus sur "l'attribution de type"? Qu'est-ce que c'est et comment est-ce que ça marche?
amorfis
25

Toutes les réponses ci-dessus sont superbes, mais il suffit d'un échantillon pour l'expliquer. C'est ici :

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]*

Alors maintenant, nous savons ce :_*qu'il faut dire au compilateur: veuillez décompresser cet argument et lier ces éléments au paramètre vararg dans l'appel de fonction plutôt que de prendre le x comme un seul argument.

Donc, en un mot, le :_*est de supprimer l'ambiguïté lorsque vous passez l'argument au paramètre vararg.

Keith
la source
6

Pour certains paresseux comme moi, cela convertit simplement un Seq en varArgs!

mani_nz
la source