Que signifient tous les opérateurs symboliques de Scala?

402

La syntaxe Scala a beaucoup de symboles. Étant donné que ces types de noms sont difficiles à trouver en utilisant les moteurs de recherche, une liste complète d'entre eux serait utile.

Quels sont tous les symboles de Scala et que fait chacun d'eux?

En particulier, je voudrais savoir sur ->, ||=, ++=, <=, _._, ::et :+=.

0__
la source
4
et l'index de Staircase 1st edition, à >> artima.com/pins1ed/book-index.html#indexanchor
Gene T
2
EN RELATION
1
aussi, s'il y a des "opérateurs" (qui sont principalement des méthodes, avec quelques noms de classe utilisés infix) que vous ne pouvez pas trouver dans scalex ou le livre d'escalier, par exemple "!!", les sources probables sont les scaladocs pour akka, scalaz et sbt
Gene T
exemple de nom de classe utilisé infixe (en allemand) >> raichoo.blogspot.com/2010/06/spass-mit-scala-infixtypen.html
Gene T
concernant la question du filtrage par les moteurs de recherche, symbolhound.com est aussi une bonne alternative
Patrick Refondini

Réponses:

526

Je divise les opérateurs, à des fins d'enseignement, en quatre catégories :

  • Mots-clés / symboles réservés
  • Méthodes importées automatiquement
  • Méthodes courantes
  • Sucres syntaxiques / composition

Il est donc heureux que la plupart des catégories soient représentées dans la question:

->    // Automatically imported method
||=   // Syntactic sugar
++=   // Syntactic sugar/composition or common method
<=    // Common method
_._   // Typo, though it's probably based on Keyword/composition
::    // Common method
:+=   // Common method

La signification exacte de la plupart de ces méthodes dépend de la classe qui les définit. Par exemple, <=on Intsignifie "inférieur ou égal à" . Le premier ->, je vais donner comme exemple ci-dessous. ::est probablement la méthode définie sur List(bien qu'elle puisse être l'objet du même nom), et :+=est probablement la méthode définie sur différentes Bufferclasses.

Alors, voyons-les.

Mots-clés / symboles réservés

Il y a quelques symboles dans Scala qui sont spéciaux. Deux d'entre eux sont considérés comme des mots clés appropriés, tandis que d'autres sont simplement "réservés". Elles sont:

// Keywords
<-  // Used on for-comprehensions, to separate pattern from generator
=>  // Used for function types, function literals and import renaming

// Reserved
( )        // Delimit expressions and parameters
[ ]        // Delimit type parameters
{ }        // Delimit blocks
.          // Method call and path separator
// /* */   // Comments
#          // Used in type notations
:          // Type ascription or context bounds
<: >: <%   // Upper, lower and view bounds
<? <!      // Start token for various XML elements
" """      // Strings
'          // Indicate symbols and characters
@          // Annotations and variable binding on pattern matching
`          // Denote constant or enable arbitrary identifiers
,          // Parameter separator
;          // Statement separator
_*         // vararg expansion
_          // Many different meanings

Ils font tous partie de la langue et, en tant que tels, peuvent être trouvés dans n'importe quel texte décrivant correctement la langue, comme la spécification Scala (PDF) elle-même.

Le dernier, le trait de soulignement, mérite une description spéciale, car il est si largement utilisé et a tellement de significations différentes. Voici un exemple:

import scala._    // Wild card -- all of Scala is imported
import scala.{ Predef => _, _ } // Exception, everything except Predef
def f[M[_]]       // Higher kinded type parameter
def f(m: M[_])    // Existential type
_ + _             // Anonymous function placeholder parameter
m _               // Eta expansion of method into method value
m(_)              // Partial function application
_ => 5            // Discarded parameter
case _ =>         // Wild card pattern -- matches anything
f(xs: _*)         // Sequence xs is passed as multiple parameters to f(ys: T*)
case Seq(xs @ _*) // Identifier xs is bound to the whole matched sequence

J'ai probablement oublié une autre signification, cependant.

Méthodes importées automatiquement

Donc, si vous n'avez pas trouvé le symbole que vous recherchez dans la liste ci-dessus, il doit s'agir d'une méthode ou d'une partie de celle-ci. Mais, souvent, vous verrez un symbole et la documentation de la classe n'aura pas cette méthode. Lorsque cela se produit, soit vous regardez une composition d'une ou plusieurs méthodes avec quelque chose d'autre, soit la méthode a été importée dans la portée, ou est disponible via une conversion implicite importée.

Ceux-ci peuvent toujours être trouvés sur ScalaDoc : il suffit de savoir où les chercher. Ou, à défaut, regardez l' index (actuellement cassé le 2.9.1, mais disponible le soir).

Chaque code Scala a trois importations automatiques:

// Not necessarily in this order
import _root_.java.lang._      // _root_ denotes an absolute path
import _root_.scala._
import _root_.scala.Predef._

Les deux premiers ne rendent disponibles que les classes et les objets singleton. Le troisième contient toutes les conversions implicites et les méthodes importées, puisqu'il Predefs'agit d'un objet lui-même.

Regarder à l'intérieur Predefmontre rapidement quelques symboles:

class <:<
class =:=
object <%<
object =:=

Tout autre symbole sera rendu disponible par une conversion implicite . Regardez simplement les méthodes marquées avec implicitqui reçoivent, en tant que paramètre, un objet de type qui reçoit la méthode. Par exemple:

"a" -> 1  // Look for an implicit from String, AnyRef, Any or type parameter

Dans le cas ci-dessus, ->est défini dans la classe ArrowAssocvia la méthode any2ArrowAssocqui prend un objet de type A, oùA est un paramètre de type illimité vers la même méthode.

Méthodes courantes

Ainsi, de nombreux symboles sont simplement des méthodes sur une classe. Par exemple, si vous le faites

List(1, 2) ++ List(3, 4)

Vous trouverez la méthode ++directement sur ScalaDoc for List . Cependant, il existe une convention que vous devez connaître lors de la recherche de méthodes. Les méthodes se terminant par deux points ( :) se lient à droite au lieu de gauche. En d'autres termes, alors que l'appel de méthode ci-dessus équivaut à:

List(1, 2).++(List(3, 4))

Si je l'avais, cela 1 :: List(2, 3)équivaudrait à:

List(2, 3).::(1)

Vous devez donc regarder le type trouvé à droite lorsque vous recherchez des méthodes se terminant par deux points. Considérez, par exemple:

1 +: List(2, 3) :+ 4

La première méthode ( +:) se lie à droite et se trouve sur List. La deuxième méthode ( :+) est juste une méthode normale, et se lie à gauche - encore une fois, surList .

Sucres syntaxiques / composition

Voici donc quelques sucres syntaxiques qui peuvent masquer une méthode:

class Example(arr: Array[Int] = Array.fill(5)(0)) {
  def apply(n: Int) = arr(n)
  def update(n: Int, v: Int) = arr(n) = v
  def a = arr(0); def a_=(v: Int) = arr(0) = v
  def b = arr(1); def b_=(v: Int) = arr(1) = v
  def c = arr(2); def c_=(v: Int) = arr(2) = v
  def d = arr(3); def d_=(v: Int) = arr(3) = v
  def e = arr(4); def e_=(v: Int) = arr(4) = v
  def +(v: Int) = new Example(arr map (_ + v))
  def unapply(n: Int) = if (arr.indices contains n) Some(arr(n)) else None
}

val Ex = new Example // or var for the last example
println(Ex(0))  // calls apply(0)
Ex(0) = 2       // calls update(0, 2)
Ex.b = 3        // calls b_=(3)
// This requires Ex to be a "val"
val Ex(c) = 2   // calls unapply(2) and assigns result to c
// This requires Ex to be a "var"
Ex += 1         // substituted for Ex = Ex + 1

La dernière est intéressante, car toute méthode symbolique peut être combinée pour former une méthode de type affectation de cette façon.

Et, bien sûr, différentes combinaisons peuvent apparaître dans le code:

(_+_) // An expression, or parameter, that is an anonymous function with
      // two parameters, used exactly where the underscores appear, and
      // which calls the "+" method on the first parameter passing the
      // second parameter as argument.
Daniel C. Sobral
la source
1
Voulez-vous dire val c = ex(2)au lieu de val ex(c) = 2?
Mike Stay
3
@MikeStay Non, je voulais dire val ex(c) = 2.
Daniel C.Sobral
Oh, il utilise la syntaxe de correspondance de motifs. Merci.
Mike Stay
=> confère également le statut 'appel par nom' lorsqu'il est utilisé entre: et tapez comme dans y: => Int '
Stephen W. Wright
1
Peut-être faut-il également mentionner les opérateurs: / et: \ vraiment peu intuitifs. Donc map.foldLeft (initialVal) est identique à (initialVal: / map) -: \ est plutôt foldRight.
Mr MT
24

Une différence (bonne, IMO) entre Scala et d'autres langues est qu'elle vous permet de nommer vos méthodes avec presque n'importe quel caractère.

Ce que vous énumérez n'est pas la «ponctuation» mais des méthodes simples et simples, et en tant que telles, leur comportement varie d'un objet à l'autre (bien qu'il existe certaines conventions).

Par exemple, consultez la documentation Scaladoc pour List , et vous verrez certaines des méthodes que vous avez mentionnées ici.

Quelques points à garder à l'esprit:

  • La plupart du temps, la A operator+equal Bcombinaison se traduit par A = A operator B, comme dans les exemples ||=ou ++=.

  • Les méthodes qui se terminent par :sont bien associatives, cela signifie que A :: Bc'est effectivement le cas B.::(A).

Vous trouverez la plupart des réponses en parcourant la documentation Scala. Garder une référence ici doublerait les efforts, et cela prendrait du retard rapidement :)

Pablo Fernandez
la source
21

Vous pouvez les regrouper en premier selon certains critères. Dans ce post, je vais simplement expliquer le caractère de soulignement et la flèche droite.

_._contient un point. Un point dans Scala indique toujours un appel de méthode . Donc, à gauche de la période, vous avez le récepteur, et à droite du message (nom de la méthode). _Est maintenant un symbole spécial dans Scala. Il y a plusieurs articles à ce sujet, par exemple cette entrée de blog tous les cas d'utilisation. Ici , c'est un raccourci de fonction anonyme , c'est-à-dire un raccourci pour une fonction qui prend un argument et appelle la méthode _dessus. Ce _n'est pas une méthode valide, donc vous avez certainement vu _._1ou quelque chose de similaire, c'est-à-dire invoquer une méthode _._1sur l'argument de la fonction. _1to _22sont les méthodes des tuples qui extraient un élément particulier d'un tuple. Exemple:

val tup = ("Hallo", 33)
tup._1 // extracts "Hallo"
tup._2 // extracts 33

Supposons maintenant un cas d'utilisation pour le raccourci de l'application de fonction. Étant donné une carte qui mappe les entiers aux chaînes:

val coll = Map(1 -> "Eins", 2 -> "Zwei", 3 -> "Drei")

Wooop, il y a déjà une autre occurrence d'une étrange ponctuation. Le tiret et les caractères supérieurs à, qui ressemblent à une flèche de droite , est un opérateur qui produit un Tuple2. Il n'y a donc aucune différence dans le résultat de l'écriture , (1, "Eins")ou 1 -> "Eins"seulement, ce dernier est plus facile à lire, en particulier dans une liste de tuples comme l'exemple de carte. Ce ->n'est pas magique, il est, comme quelques autres opérateurs, disponible parce que vous avez toutes les conversions implicites en objet scala.Predefdans la portée. La conversion qui a lieu ici est

implicit def any2ArrowAssoc [A] (x: A): ArrowAssoc[A] 

ArrowAssoca la ->méthode qui crée le Tuple2. Ainsi 1 -> "Eins"est l'appel réel Predef.any2ArrowAssoc(1).->("Eins"). D'accord. Revenons maintenant à la question d'origine avec le caractère de soulignement:

// lets create a sequence from the map by returning the
// values in reverse.
coll.map(_._2.reverse) // yields List(sniE, iewZ, ierD)

Le trait de soulignement ici raccourcit le code équivalent suivant:

coll.map(tup => tup._2.reverse)

Notez que la mapméthode d'une carte passe le tuple de clé et de valeur à l'argument de fonction. Comme nous ne nous intéressons qu'aux valeurs (les chaînes), nous les extrayons avec la _2méthode sur le tuple.

0__
la source
+1 J'avais du mal à comprendre la ->méthode mais votre phrase "Il n'y a donc aucune différence dans le résultat de l'écriture (1, "Eins")ou 1 -> "Eins"" m'a aidé à comprendre la syntaxe et son utilisation.
Jesse Webb
fyi le lien de votre entrée de blog est mort
still_learning
15

En plus des réponses brillantes de Daniel et 0__, je dois dire que Scala comprend les analogues Unicode pour certains des symboles, donc au lieu de

for (n <- 1 to 10) n % 2 match {
  case 0 => println("even")
  case 1 => println("odd")
}

on peut écrire

for (n ← 1 to 10) n % 2 match {
  case 0 ⇒ println("even")
  case 1 ⇒ println("odd")
}
Om Nom Nom
la source
10

Concernant ::il y a une autre entrée Stackoverflow qui couvre le ::cas. En bref, il est utilisé pour construire Listspar « consing » un élément de tête et une liste de queue. C'est à la fois une classe qui représente une liste cons'ed et qui peut être utilisée comme extracteur, mais le plus souvent c'est une méthode sur une liste. Comme le souligne Pablo Fernandez, car il se termine par deux points, il est associatif à droite , ce qui signifie que le récepteur de l'appel de méthode est à droite et l'argument à gauche de l'opérateur. De cette façon, vous pouvez exprimer élégamment le contre en ajoutant un nouvel élément head à une liste existante:

val x = 2 :: 3 :: Nil  // same result as List(2, 3)
val y = 1 :: x         // yields List(1, 2, 3)

Cela équivaut à

val x = Nil.::(3).::(2) // successively prepend 3 and 2 to an empty list
val y = x.::(1)         // then prepend 1

L'utilisation comme objet extracteur est la suivante:

def extract(l: List[Int]) = l match {
   case Nil          => "empty"
   case head :: Nil  => "exactly one element (" + head + ")"
   case head :: tail => "more than one element"
}

extract(Nil)          // yields "empty"
extract(List(1))      // yields "exactly one element (1)"
extract(List(2, 3))   // yields "more than one element"

Cela ressemble à un opérateur ici, mais c'est vraiment juste une autre façon (plus lisible) d'écrire

def extract2(l: List[Int]) = l match {
   case Nil            => "empty"
   case ::(head, Nil)  => "exactly one element (" + head + ")"
   case ::(head, tail) => "more than one element"
}

Vous pouvez en savoir plus sur les extracteurs dans cet article .

0__
la source
9

<=est comme si vous le lisiez: «inférieur ou égal». C'est donc un opérateur mathématique, dans la liste de <(est inférieur à?), >(Est supérieur à ==?), !=(Est égal à?), <=( N'est pas égal?), (Est inférieur ou égal?), Et>= (est supérieur à ou égal?).

Cela ne doit pas être confondu avec =>ce qui est une sorte de double flèche à droite , utilisée pour séparer la liste d'arguments du corps d'une fonction et pour séparer la condition de test dans la correspondance de modèle (un casebloc) du corps exécuté lorsqu'une correspondance se produit . Vous pouvez voir un exemple de cela dans mes deux réponses précédentes. Tout d'abord, la fonction utilise:

coll.map(tup => tup._2.reverse)

qui est déjà abrégé car les types sont omis. La fonction de suivi serait

// function arguments         function body
(tup: Tuple2[Int, String]) => tup._2.reverse

et l'utilisation de correspondance de modèle:

def extract2(l: List[Int]) = l match {
   // if l matches Nil    return "empty"
   case Nil            => "empty"
   // etc.
   case ::(head, Nil)  => "exactly one element (" + head + ")"
   // etc.
   case ::(head, tail) => "more than one element"
}
0__
la source
4
Pour éviter cette confusion, j'ai décidé de commencer à utiliser les caractères unicode pour la double flèche droite (\ U21D2), la flèche droite "maps" unique (\ U2192) et la flèche unique "in" gauche (\ U2190). Scala soutient cela, mais j'étais un peu sceptique jusqu'à ce que je l'essaie pendant un moment. Recherchez simplement comment lier ces points de code à une combinaison de touches pratique sur votre système. C'était vraiment facile sur OS X.
Connor Doyle
5

Je considère qu'un IDE moderne est essentiel pour comprendre les grands projets de scala. Étant donné que ces opérateurs sont également des méthodes, dans l'idée intellij je viens de contrôler-cliquer ou contrôler-b dans les définitions.

Vous pouvez cliquer avec le bouton droit de la souris sur un opérateur contre (: :) et vous retrouver à la scala javadoc en disant "Ajoute un élément au début de cette liste." Dans les opérateurs définis par l'utilisateur, cela devient encore plus critique, car ils pourraient être définis dans des implicits difficiles à trouver ... votre IDE sait où l'implicite a été défini.

nairbv
la source
4

Ajoutant simplement aux autres excellentes réponses. Scala propose deux opérateurs symboliques souvent critiqués, les opérateurs /:( foldLeft) et :\( foldRight), le premier étant associatif à droite. Ainsi, les trois déclarations suivantes sont équivalentes:

( 1 to 100 ).foldLeft( 0, _+_ )
( 1 to 100 )./:( 0 )( _+_ )
( 0 /: ( 1 to 100 ) )( _+_ )

Tout comme ces trois:

( 1 to 100 ).foldRight( 0, _+_ )
( 1 to 100 ).:\( 0 )( _+_ )
( ( 1 to 100 ) :\ 0 )( _+_ )
Mr MT
la source
2

Scala hérite de la plupart des opérateurs arithmétiques de Java . Cela inclut au niveau du bit ou |(caractère de canal unique), au niveau du bit et &, au niveau du bit exclusif ou ^, ainsi que logique (booléen) ou ||(deux caractères de canal) et logique et &&. Fait intéressant, vous pouvez utiliser les opérateurs à caractère unique boolean, de sorte que les opérateurs logiques java'ish sont totalement redondants:

true && true   // valid
true & true    // valid as well

3 & 4          // bitwise-and (011 & 100 yields 000)
3 && 4         // not valid

Comme indiqué dans un autre article, les appels se terminant par un signe égal =sont résolus (si une méthode avec ce nom n'existe pas!) Par une réaffectation:

var x = 3
x += 1         // `+=` is not a method in `int`, Scala makes it `x = x + 1`

Ce «double contrôle» permet, d'échanger facilement un mutable contre une collection immuable:

val m = collection.mutable.Set("Hallo")   // `m` a val, but holds mutable coll
var i = collection.immutable.Set("Hallo") // `i` is a var, but holds immutable coll

m += "Welt" // destructive call m.+=("Welt")
i += "Welt" // re-assignment i = i + "Welt" (creates a new immutable Set)
0__
la source
4
PS Il existe une différence entre l'utilisation des opérateurs de caractère simple et double sur les booléens - le premier est impatient (tous les termes sont évalués), le second se termine tôt si le booléen résultant est connu: true | { println( "Icke" ); true }⇒ imprime! true || { println( "Icke" ); true }⇒ n'imprime pas !
2012