La syntaxe Scala a beaucoup de symboles. Étant donné que ces types de noms sont difficiles à trouver à l’aide des moteurs de recherche, une liste complète de ces noms serait utile.
Quels sont tous les symboles de Scala et que font chacun d'eux?
En particulier, j'aimerais connaître ->
, ||=
, ++=
, <=
, _._
, ::
et :+=
.
Je divise les opérateurs, dans le but d’enseigner, en quatre catégories :
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, <=
sur Int
signifie "inférieur ou égal à" . Le premier, ->
, je vais donner l'exemple ci-dessous. ::
est probablement la méthode définie sur List
(bien que puisse être l’objet du même nom) et :+=
est probablement la méthode définie à Buffer
classes.
Alors voyons-les.
Certains symboles dans Scala 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
Celles-ci font toutes une partie du langage et, en tant que telles, peuvent être trouvées dans tout texte décrivant correctement le langage, tel que Spécification Scala (PDF ) lui-même.
Le dernier, le trait de soulignement, mérite une description spéciale, car il est très utilisé et a plusieurs significations. 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é un autre sens, cependant.
Ainsi, si vous ne trouvez pas 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, vous étudiez la composition d'une ou de plusieurs méthodes avec autre chose, ou bien la méthode a été importée dans scope ou est disponible via une conversion implicite importée.
Ceux-ci sont toujours disponibles sur ScalaDoc : il vous suffit de savoir où les chercher. Ou, à défaut, regardez le index (actuellement interrompu 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 rendent uniquement les classes et les objets singleton disponibles. Le troisième contient toutes les conversions implicites et les méthodes importées, puisque Predef
est un objet lui-même.
En regardant à l'intérieur, Predef
affiche rapidement quelques symboles:
class <:<
class =:=
object <%<
object =:=
Tout autre symbole sera rendu disponible via une conversion implicite . Il suffit de regarder les méthodes étiquetées avec implicit
qui 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 ArrowAssoc
par la méthode any2ArrowAssoc
qui prend un objet de type A
, où A
est un paramètre de type sans limite pour la même méthode. .
Ainsi, beaucoup de 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 ++
à la droite du ScalaDoc pour List . Cependant, il existe une convention que vous devez connaître lorsque vous recherchez des méthodes. Les méthodes se terminant par deux points (:
) sont liées à droite au lieu de gauche. En d'autres termes, alors que l'appel de la méthode ci-dessus est équivalent à:
List(1, 2).++(List(3, 4))
Si j’avais plutôt 1 :: List(2, 3)
, cela é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érons, 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 une méthode normale et se lie à gauche, là encore sur List
.
Alors, voici quelques sucres syntaxiques qui peuvent cacher 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 semblable à une affectation.
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.
Une différence (bonne, à l’OMI) entre Scala et d’autres langues est qu’elle vous permet de nommer vos méthodes avec presque tous les caractères.
Ce que vous énumérez n’est pas une "ponctuation", mais des méthodes simples et simples; leur comportement varie donc d’un objet à l’autre (même s’il existe certaines conventions).
Par exemple, consultez le documentation de 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 combinaison A operator+equal B
se traduit par A = A operator B
, comme dans les exemples ||=
ou ++=
.
Les méthodes qui se terminent par :
sont associatives à droite, cela signifie que A :: B
est en réalité B.::(A)
.
Vous trouverez la plupart des réponses en parcourant la documentation Scala. Garder une référence ici ferait double emploi, et cela prendrait du retard rapidement :)
Vous pouvez les regrouper d’abord en fonction de certains critères. Dans cet article, je vais simplement expliquer le caractère de soulignement et la flèche vers la droite.
__._
_ contient un point. Un point dans Scala indique toujours un appel de méthode . Donc, à gauche de la période où vous avez le destinataire, et à droite le message (nom de la méthode). Maintenant __
_ est un symbole spécial en Scala. Il y a plusieurs articles à ce sujet, par exemple cette entrée de blog tous les cas d'utilisation. Voici un raccourci de fonction anonyme , c’est-à-dire un raccourci pour une fonction qui prend un argument et appelle la méthode __
_ . Maintenant, __
_ n'est pas une méthode valide. Vous avez donc certainement vu __._1
_ ou quelque chose de similaire, c'est-à-dire que vous appelez la méthode __._1
_ sur l'argument de la fonction. __1
_ à __22
_ sont les méthodes de n-uplets 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 d'application de fonction. Étant donné une carte qui mappe des entiers à des chaînes:
_val coll = Map(1 -> "Eins", 2 -> "Zwei", 3 -> "Drei")
_
Wooop, il y a déjà une autre occurrence d'une ponctuation étrange. Les traits d'union et supérieur à, qui ressemblent à une flèche vers la droite , est un opérateur produisant un Tuple2
. Donc, il n'y a aucune différence dans le résultat de l'écriture de _(1, "Eins")
_ ou de _1 -> "Eins"
_, seulement que ce dernier est plus facile à lire, en particulier dans une liste de n-uplets comme l'exemple de carte. Le _->
_ n'est pas une magie, il est, comme quelques autres opérateurs, disponible parce que vous avez toutes les conversions implicite dans l'objet scala.Predef
dans la portée. La conversion qui a lieu ici est
_implicit def any2ArrowAssoc [A] (x: A): ArrowAssoc[A]
_
Où ArrowAssoc
a la méthode _->
_ qui crée le _Tuple2
_. Donc _1 -> "Eins"
_ est l'appel en cours Predef.any2ArrowAssoc(1).->("Eins")
. D'accord. Revenons maintenant à la question initiale 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 méthode map
d'une carte passe dans le tuple de clé et de valeur à l'argument de la fonction. Puisque nous ne nous intéressons qu'aux valeurs (les chaînes), nous les extrayons avec la méthode __2
_ sur le Tuple.
En plus des réponses brillantes de Daniel et 0__, je dois dire que Scala comprend nicode analogues 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")
}
En ce qui concerne ::
, il existe une autre entrée Stackoverflow qui couvre le cas ::
. En bref, il est utilisé pour construire Lists
en ' en utilisant ' un élément head et une liste de contrôle. C'est à la fois une classe qui représente une liste contre et qui peut être utilisée comme extracteur, mais le plus souvent c'est un méthode sur une liste. Comme le souligne Pablo Fernandez, dans la mesure où il se termine par un signe deux-points, il est associatif droit , ce qui signifie que le destinataire de l'appel de méthode est à droite et que argument à gauche de l'opérateur. De cette façon, vous pouvez exprimer élégamment le contenu par 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)
Ceci est équivalent à
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 d'extraction 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 (33)"
extract(List(2, 3)) // yields "more than one element"
Cela ressemble à un opérateur ici, mais ce n’est vraiment qu’une autre manière d’écrire (plus lisible)
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 this post .
<=
est comme vous le "liriez": "inférieur ou égal à". C'est donc un opérateur mathématique, dans la liste de <
(est inférieur à?), >
(est supérieur à?), ==
(égal?), !=
( n'est pas égal?), <=
(est inférieur ou égal?), et >=
(est supérieur ou égal?).
Cela ne doit pas être confondu avec =>
qui est une sorte de double flèche vers la droite, utilisé pour séparer la liste d'arguments du corps d'une fonction et de séparer la condition de test dans la recherche de correspondance (un bloc case
) du corps exécuté lorsqu'une correspondance est trouvée. Vous pouvez en voir un exemple 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 suivante serait
// function arguments function body
(tup: Tuple2[Int, String]) => tup._2.reverse
et l'utilisation du filtrage:
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"
}
Je considère qu'un IDE moderne est essentiel à la compréhension de grands scala projets. Puisque ces opérateurs sont aussi des méthodes, dans intellij idée, je clique tout simplement sur "control" ou "control-b" dans les définitions.
Vous pouvez faire un clic droit dans un opérateur contre (: :) et arriver au scala javadoc 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 implicites difficiles à trouver ... votre IDE sait où l'emplacement de l'implicite a été défini.
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 à droite associative. Donc, les trois déclarations suivantes sont l’équivalent:
( 1 to 100 ).foldLeft( 0, _+_ )
( 1 to 100 )./:( 0 )( _+_ )
( 0 /: ( 1 to 100 ) )( _+_ )
Comme sont ces trois:
( 1 to 100 ).foldRight( 0, _+_ )
( 1 to 100 ).:\( 0 )( _+_ )
( ( 1 to 100 ) :\ 0 )( _+_ )
Scala hérite de la plupart des opérateurs arithmétiques de Java . Ceci inclut bitwise ou |
(caractère de conduite unique), bitwise et &
, bitwise-exclusive-ou ^
, ainsi que logique (booléen) ou ||
( deux caractères de canal) et logical-and &&
. Fait intéressant, vous pouvez utiliser les opérateurs à caractère unique sur 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 une autre publication, les appels se terminant par un signe égal =
, sont résolus (si une méthode portant 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, pour é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)