Cosa significano tutti gli operatori simbolici di Scala?


402

La sintassi di Scala ha molti simboli. Poiché questi tipi di nomi sono difficili da trovare utilizzando i motori di ricerca, un elenco completo di questi sarebbe utile.

Quali sono tutti i simboli di Scala e cosa fanno ciascuno di essi?

In particolare, mi piacerebbe sapere ->, ||=, ++=, <=, _._, ::, e :+=.


4
e l'indice di Staircase 1st edition, su >> artima.com/pins1ed/book-index.html#indexanchor
Gene T

2
Correlati: caratteri operatore vs caratteri alfanumerici: stackoverflow.com/questions/7656937/...
Luigi Plinge

1
inoltre, se ci sono "operatori" (che sono per lo più metodi, con alcuni nomi di classe usati infix) che non puoi trovare in scalex o nel libro delle scale, ad esempio "!!", le fonti probabili sono gli scaladoc per akka, scalaz e sbt
Gene T

esempio di nome classe usato infix (in tedesco) >> raichoo.blogspot.com/2010/06/spass-mit-scala-infixtypen.html
Gene T

per quanto riguarda il problema del filtraggio da parte dei motori di ricerca, symbolhound.com è anche una valida alternativa
Patrick Refondini,

Risposte:


526

Divido gli operatori, ai fini dell'insegnamento, in quattro categorie :

  • Parole chiave / simboli riservati
  • Metodi importati automaticamente
  • Metodi comuni
  • Zuccheri sintattici / composizione

È una fortuna, quindi, che la maggior parte delle categorie siano rappresentate nella domanda:

->    // 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

Il significato esatto della maggior parte di questi metodi dipende dalla classe che li sta definendo. Ad esempio, <=on Intsignifica "minore o uguale a" . Il primo, ->ti darò come esempio di seguito. ::è probabilmente il metodo definito List(anche se potrebbe essere l'oggetto con lo stesso nome) ed :+=è probabilmente il metodo definito su varie Bufferclassi.

Quindi, vediamoli.

Parole chiave / simboli riservati

Ci sono alcuni simboli in Scala che sono speciali. Due di questi sono considerati parole chiave appropriate, mentre altri sono semplicemente "riservati". Loro sono:

// 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

Questi fanno tutti parte della lingua e, come tali, possono essere trovati in qualsiasi testo che descriva correttamente la lingua, come la specifica Scala (PDF) stessa.

L'ultimo, il carattere di sottolineatura, merita una descrizione speciale, perché è così ampiamente usato e ha tanti significati diversi. Ecco un esempio:

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

Probabilmente ho dimenticato qualche altro significato, però.

Metodi importati automaticamente

Quindi, se non hai trovato il simbolo che stai cercando nell'elenco sopra, allora deve essere un metodo o parte di uno. Ma spesso vedrai alcuni simboli e la documentazione per la classe non avrà quel metodo. Quando ciò accade, stai osservando una composizione di uno o più metodi con qualcos'altro, oppure il metodo è stato importato nell'ambito o è disponibile tramite una conversione implicita importata.

Questi possono ancora essere trovati su ScalaDoc : devi solo sapere dove cercarli. Oppure, in mancanza di ciò, guarda l' indice (attualmente rotto su 2.9.1, ma disponibile di notte).

Ogni codice Scala ha tre importazioni automatiche:

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

I primi due rendono disponibili solo le classi e gli oggetti singleton. Il terzo contiene tutte le conversioni implicite e i metodi importati, poiché Predefè un oggetto stesso.

Guardando dentro Predefmostra rapidamente alcuni simboli:

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

Qualsiasi altro simbolo sarà reso disponibile attraverso una conversione implicita . Basta guardare i metodi taggati con implicitquel ricevere, come parametro, un oggetto di tipo che sta ricevendo il metodo. Per esempio:

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

Nel caso precedente, ->viene definito nella classe ArrowAssoctramite il metodo any2ArrowAssocche accetta un oggetto di tipo A, dove Aè un parametro di tipo non associato allo stesso metodo.

Metodi comuni

Quindi, molti simboli sono semplicemente metodi su una classe. Ad esempio, se lo fai

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

Troverai il metodo ++direttamente su ScalaDoc for List . Tuttavia, c'è una convenzione che devi conoscere quando cerchi i metodi. I metodi che terminano con due punti ( :) si legano a destra anziché a sinistra. In altre parole, mentre la chiamata del metodo sopra è equivalente a:

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

Se avessi, invece 1 :: List(2, 3), sarebbe equivalente a:

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

Quindi devi guardare il tipo trovato a destra quando cerchi i metodi che terminano con i due punti. Considera, ad esempio:

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

Il primo metodo ( +:) si lega a destra e si trova su List. Il secondo metodo ( :+) è solo un metodo normale e si lega a sinistra - di nuovo, su List.

Zuccheri sintattici / composizione

Quindi, ecco alcuni zuccheri sintattici che potrebbero nascondere un metodo:

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

L'ultimo è interessante, perché qualsiasi metodo simbolico può essere combinato per formare in questo modo un metodo simile a un incarico.

E, naturalmente, ci sono varie combinazioni che possono apparire nel codice:

(_+_) // 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.

1
Intendevi val c = ex(2)invece di val ex(c) = 2?
Mike Soggiorno

3
@MikeStay No, intendevo dire val ex(c) = 2.
Daniel C. Sobral,

Oh, sta usando la sintassi del pattern matching. Grazie.
Mike Soggiorno

=> conferisce anche lo stato 'chiama per nome' se usato tra: e digita come in y: => Int '
Stephen W. Wright

1
Forse si dovrebbe anche menzionare: / e: \ operatori davvero non intuitivi. Quindi map.foldLeft (initialVal) è uguale a (initialVal: / map) -: \ è invece FoldRight.
Mr MT,

24

Una (buona, IMO) differenza tra Scala e altre lingue è che ti permette di nominare i tuoi metodi con quasi tutti i caratteri.

Ciò che elenchi non è "punteggiatura" ma metodi chiari e semplici, e come tale il loro comportamento varia da un oggetto all'altro (anche se ci sono alcune convenzioni).

Ad esempio, controlla la documentazione di Scaladoc per Elenco e vedrai alcuni dei metodi che hai citato qui.

Alcune cose da tenere a mente:

  • Il più delle volte la A operator+equal Bcombinazione si traduce A = A operator B, come negli esempi ||=o ++=.

  • I metodi che finiscono in :sono associativi giusti, questo significa che A :: Bè effettivamente B.::(A).

Troverai la maggior parte delle risposte sfogliando la documentazione di Scala. Mantenere un riferimento qui duplicherà gli sforzi e rimarrebbe rapidamente indietro :)


21

Puoi raggrupparli per primi in base ad alcuni criteri. In questo post spiegherò solo il carattere di sottolineatura e la freccia destra.

_._contiene un punto. Un punto in Scala indica sempre una chiamata di metodo . Quindi a sinistra del periodo hai il destinatario e a destra il messaggio (nome del metodo). Ora _è un simbolo speciale in Scala. Ci sono molti post a riguardo, ad esempio in questo post di blog tutti i casi d'uso. Qui è una scorciatoia di funzione anonima , ovvero una scorciatoia per una funzione che accetta un argomento e invoca il metodo _su di esso. Ora _non è un metodo valido, quindi sicuramente stavi vedendo _._1o qualcosa di simile, cioè invocando il metodo _._1sull'argomento della funzione. _1per _22sono i metodi di tuple grado di estrarre un elemento particolare di una tupla. Esempio:

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

Ora ipotizziamo un caso d'uso per il collegamento dell'applicazione di funzione. Data una mappa che mappa numeri interi alle stringhe:

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

Wooop, c'è già un'altra ricorrenza di una strana punteggiatura. Il trattino e i caratteri maggiore di, che assomigliano a una freccia a destra , sono un operatore che produce a Tuple2. Quindi non c'è alcuna differenza nell'esito della scrittura (1, "Eins")o 1 -> "Eins", solo che quest'ultimo è più facile da leggere, specialmente in un elenco di tuple come l'esempio della mappa. Non ->è una magia, è, come pochi altri operatori, disponibile perché hai tutte le conversioni implicite nell'oggetto scala.Predefnell'ambito. La conversione che ha luogo qui è

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

Dove ArrowAssocha il ->metodo che crea il Tuple2. Quindi 1 -> "Eins"è effettiva la chiamata Predef.any2ArrowAssoc(1).->("Eins"). Ok. Ora torniamo alla domanda originale con il carattere di sottolineatura:

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

Il trattino basso qui abbrevia il seguente codice equivalente:

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

Si noti che il mapmetodo di una mappa passa nella tupla di chiave e valore all'argomento della funzione. Poiché siamo interessati solo ai valori (le stringhe), li estraiamo con il _2metodo sulla tupla.


+1 Avevo problemi a cercare di capire il ->metodo, ma la tua frase "Quindi non c'è alcuna differenza nell'esito della scrittura (1, "Eins")o 1 -> "Eins"" mi ha aiutato a comprendere la sintassi e il suo utilizzo.
Jesse Webb,

a proposito, il link al tuo blog è morto
still_learning

15

In aggiunta alle brillanti risposte di Daniel e 0__, devo dire che Scala comprende gli analoghi Unicode per alcuni dei simboli, quindi invece di

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

si può scrivere

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

10

Per quanto riguarda ::c'è un'altra voce StackOverflow che copre il ::caso. In breve, viene utilizzato per costruire Lists" consing " un elemento head e un elenco di coda. È sia una classe che rappresenta un elenco di esperti che può essere utilizzato come estrattore, ma più comunemente è un metodo in un elenco. Come sottolinea Pablo Fernandez, poiché termina in due punti, è associativo giusto , il che significa che il destinatario della chiamata del metodo è a destra e l'argomento a sinistra dell'operatore. In questo modo si può esprimere l'eleganza consing come anteponendo un nuovo elemento di testa a un elenco esistente:

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

Questo equivale a

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

L'uso come oggetto estrattore è il seguente:

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"

Qui sembra un operatore, ma in realtà è solo un altro (più leggibile) modo di scrivere

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

Puoi leggere di più sugli estrattori in questo post .


9

<=è come se lo "leggessi": "minore di o uguale a". Quindi è un operatore matematico, nell'elenco di <(è minore di?), >(È maggiore di?), ==(Uguale?), !=(Non è uguale?), <=(È minore o uguale?) E >=(è maggiore di o uguale?).

Questo non deve essere confuso con il =>quale è una specie di doppia freccia a destra , utilizzata per separare l'elenco degli argomenti dal corpo di una funzione e per separare la condizione di test nella corrispondenza del modello (un caseblocco) dal corpo eseguito quando si verifica una corrispondenza . Puoi vedere un esempio di questo nelle mie due precedenti risposte. Innanzitutto, la funzione utilizza:

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

che è già abbreviato quando i tipi vengono omessi. La seguente funzione sarebbe

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

e la corrispondenza del modello usa:

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

4
Evitare questa confusione è il motivo per cui ho deciso di iniziare a utilizzare i caratteri unicode per la doppia freccia destra (\ U21D2), la freccia singola "mappe" destra (\ U2192) e la freccia singola "in" sinistra (\ U2190). Scala supporta questo, ma ero un po 'scettico fino a quando non l'ho provato per un po'. Basta cercare come associare questi punti di codice a una comoda combinazione di tasti sul proprio sistema. È stato davvero facile su OS X.
Connor Doyle,

5

Considero un IDE moderno fondamentale per la comprensione dei grandi progetti scala. Dato che questi operatori sono anche metodi, nell'idea di intellij ho semplicemente control-click o control-b nelle definizioni.

Puoi fare clic con il tasto destro del mouse su un operatore contro (: :) e finire su scala javadoc dicendo "Aggiunge un elemento all'inizio di questo elenco." Negli operatori definiti dall'utente, questo diventa ancora più critico, dal momento che potrebbero essere definiti in impliciti difficili da trovare ... il tuo IDE sa dove è stato definito l'implicito.


4

Aggiungo solo alle altre risposte eccellenti. Scala offre due operatori simbolici spesso criticati, /:( foldLeft) e :\( foldRight) operatori, il primo essendo associativo di destra. Quindi le seguenti tre affermazioni sono l'equivalente:

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

Come sono questi tre:

( 1 to 100 ).foldRight( 0, _+_ )
( 1 to 100 ).:\( 0 )( _+_ )
( ( 1 to 100 ) :\ 0 )( _+_ )

2

Scala eredita la maggior parte degli operatori aritmetici di Java . Questo include bit a bit o |(carattere di pipe singolo), bit a bit e &, bit a bit esclusivo o oppure ^logico (booleano) o ||(due caratteri di pipe) e logico ee &&. È interessante notare che è possibile utilizzare gli operatori a carattere singolo su boolean, quindi gli operatori logici java sono totalmente ridondanti:

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

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

Come sottolineato in un altro post, le chiamate che terminano con un segno di uguale =sono risolte (se non esiste un metodo con quel nome!) Mediante una riassegnazione:

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

Questo "doppio controllo" rende possibile, scambiare facilmente un mutevole con una collezione immutabile:

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)

4
PS C'è una differenza tra l'uso degli operatori a carattere singolo o doppio sui valori booleani: il primo è desideroso (vengono valutati tutti i termini), il secondo termina in anticipo se si conosce il valore booleano risultante: true | { println( "Icke" ); true }⇒ stampe! true || { println( "Icke" ); true }non stampa!
0__
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.