Was bedeuten alle symbolischen Operatoren von Scala?

402

Die Scala-Syntax enthält viele Symbole. Da solche Namen mit Suchmaschinen schwer zu finden sind, wäre eine umfassende Liste hilfreich.

Was sind alle Symbole in Scala und was macht jedes von ihnen?

Insbesondere würde Ich mag darüber wissen ->, ||=, ++=, <=, _._, ::, und :+=.

0__
quelle
4
und der Index der 1. Ausgabe von Staircase unter >> artima.com/pins1ed/book-index.html#indexanchor
Gene T
2
Verwandte: Operator Zeichen vs alphanumerische Zeichen: stackoverflow.com/questions/7656937/…
Luigi Plinge
1
Auch wenn es "Operatoren" gibt (meistens Methoden, bei denen einige Klassennamen als Infix verwendet werden), die Sie in scalex oder im Treppenbuch nicht finden können, z. B. "!!", sind die Skaladocs für akka, scalaz wahrscheinlich Quellen und sbt
Gene T
Beispiel für den verwendeten Klassennamen Infix >> raichoo.blogspot.com/2010/06/spass-mit-scala-infixtypen.html
Gene T
In Bezug auf das Problem der Filterung durch Suchmaschinen ist symbolhound.com auch eine
gute

Antworten:

526

Ich teile die Operatoren zum Zweck des Unterrichts in vier Kategorien ein :

  • Schlüsselwörter / reservierte Symbole
  • Automatisch importierte Methoden
  • Gängige Methoden
  • Syntaktische Zucker / Zusammensetzung

Es ist also ein Glück, dass die meisten Kategorien in der Frage vertreten sind:

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

Die genaue Bedeutung der meisten dieser Methoden hängt von der Klasse ab, die sie definiert. Zum Beispiel bedeutet <=on "kleiner oder gleich" . Das erste werde ich als Beispiel unten geben. ist wahrscheinlich die Methode, für die definiert wurde (obwohl es sich um das gleichnamige Objekt handeln könnte ), und wahrscheinlich die Methode, die für verschiedene Klassen definiert wurde .Int->::List:+=Buffer

Also, lasst uns sie sehen.

Schlüsselwörter / reservierte Symbole

Es gibt einige Symbole in Scala, die besonders sind. Zwei von ihnen werden als richtige Schlüsselwörter angesehen, während andere nur "reserviert" sind. Sie sind:

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

Diese sind alle Teil der Sprache und können als solche in jedem Text gefunden werden, der die Sprache richtig beschreibt, wie z. B. Scala Specification (PDF) selbst.

Der letzte, der Unterstrich, verdient eine besondere Beschreibung, weil er so weit verbreitet ist und so viele verschiedene Bedeutungen hat. Hier ist ein Beispiel:

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

Wahrscheinlich habe ich jedoch eine andere Bedeutung vergessen.

Automatisch importierte Methoden

Wenn Sie das gesuchte Symbol in der obigen Liste nicht gefunden haben, muss es eine Methode oder ein Teil davon sein. Oft wird jedoch ein Symbol angezeigt, und die Dokumentation für die Klasse enthält diese Methode nicht. In diesem Fall betrachten Sie entweder eine Zusammensetzung einer oder mehrerer Methoden mit etwas anderem, oder die Methode wurde in den Gültigkeitsbereich importiert oder ist über eine importierte implizite Konvertierung verfügbar.

Diese sind immer noch auf ScalaDoc zu finden : Sie müssen nur wissen, wo Sie sie suchen müssen. Andernfalls sehen Sie sich den Index an (derzeit auf 2.9.1 defekt, aber abends verfügbar).

Jeder Scala-Code verfügt über drei automatische Importe:

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

Die ersten beiden stellen nur Klassen und Singleton-Objekte zur Verfügung. Die dritte enthält alle impliziten Konvertierungen und importierten Methoden, da Predefes sich um ein Objekt selbst handelt.

Wenn Sie Predefschnell hineinschauen, sehen Sie einige Symbole:

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

Jedes andere Symbol wird durch eine implizite Konvertierung verfügbar gemacht . Schauen Sie sich einfach die mit implicitdiesem Tag gekennzeichneten Methoden an , die als Parameter ein Objekt vom Typ erhalten, das die Methode empfängt. Zum Beispiel:

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

Im obigen Fall ->wird in der Klasse ArrowAssocdurch die Methode definiert any2ArrowAssoc, die ein Objekt vom Typ übernimmt A, wobei Aein unbegrenzter Typparameter für dieselbe Methode gilt.

Gängige Methoden

Viele Symbole sind also einfach Methoden für eine Klasse. Zum Beispiel, wenn Sie dies tun

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

Sie finden die Methode ++direkt in der ScalaDoc for List . Es gibt jedoch eine Konvention, die Sie bei der Suche nach Methoden beachten müssen. Methoden, die mit Doppelpunkt ( :) enden , werden rechts statt links gebunden. Mit anderen Worten, während der obige Methodenaufruf äquivalent ist zu:

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

Wenn ich stattdessen 1 :: List(2, 3)hätte, wäre das gleichbedeutend mit:

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

Sie müssen sich also den Typ auf der rechten Seite ansehen, wenn Sie nach Methoden suchen, die mit einem Doppelpunkt enden. Betrachten Sie zum Beispiel:

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

Die erste Methode ( +:) bindet nach rechts und befindet sich auf List. Die zweite Methode ( :+) ist nur eine normale Methode und bindet nach links - wieder auf List.

Syntaktische Zucker / Zusammensetzung

Hier sind einige syntaktische Zucker, die eine Methode verbergen können:

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

Die letzte ist interessant, weil jede symbolische Methode auf diese Weise zu einer zuweisungsähnlichen Methode kombiniert werden kann.

Und natürlich gibt es verschiedene Kombinationen, die im Code erscheinen können:

(_+_) // 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
quelle
1
Meinten Sie val c = ex(2)statt val ex(c) = 2?
Mike Stay
3
@ MikeStay Nein, ich meinte val ex(c) = 2.
Daniel C. Sobral
Oh, es wird eine Pattern-Matching-Syntax verwendet. Vielen Dank.
Mike Stay
=> Verleiht auch den Status "Anruf nach Namen", wenn zwischen: und Typ wie in y: => Int '
Stephen W. Wright
1
Vielleicht sollte man auch die wirklich unintuitiven Operatoren: / und: \ erwähnen. Map.foldLeft (initialVal) ist also dasselbe wie (initialVal: / map) -: \ ist stattdessen foldRight.
Herr MT
24

Ein (guter, IMO) Unterschied zwischen Scala und anderen Sprachen besteht darin, dass Sie Ihre Methoden mit fast jedem Zeichen benennen können.

Was Sie aufzählen, ist keine "Interpunktion", sondern einfache Methoden, und als solche variieren ihre Verhaltensweisen von Objekt zu Objekt (obwohl es einige Konventionen gibt).

Überprüfen Sie beispielsweise die Scaladoc-Dokumentation für List , und Sie werden einige der hier erwähnten Methoden sehen.

Einige Dinge zu beachten:

  • In den meisten Fällen wird die A operator+equal BKombination A = A operator Bwie in den Beispielen ||=oder übersetzt ++=.

  • Methoden, die mit enden, :sind richtig assoziativ, das heißt, das A :: Bist tatsächlich so B.::(A).

Die meisten Antworten finden Sie in der Scala-Dokumentation. Eine Referenz hier zu behalten würde doppelte Anstrengungen bedeuten und schnell ins Hintertreffen geraten :)

Pablo Fernandez
quelle
21

Sie können diese zuerst nach bestimmten Kriterien gruppieren. In diesem Beitrag erkläre ich nur den Unterstrich und den Rechtspfeil.

_._enthält einen Punkt. Ein Punkt in Scala zeigt immer einen Methodenaufruf an . Also links von dem Zeitraum, in dem Sie den Empfänger haben, und rechts davon die Nachricht (Methodenname). Jetzt _ist ein besonderes Symbol in Scala. Es gibt mehrere Beiträge dazu, zum Beispiel dieser Blogeintrag alle Anwendungsfälle. Hier handelt es sich um eine anonyme Funktionsverknüpfung , dh um eine Verknüpfung für eine Funktion, die ein Argument verwendet und die Methode _darauf aufruft . Jetzt _ist keine gültige Methode, also haben Sie mit Sicherheit _._1etwas Ähnliches gesehen, dh eine Methode _._1für das Funktionsargument aufgerufen. _1bis _22sind die Methoden von Tupeln , die ein bestimmtes Element eines Tupels extrahieren. Beispiel:

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

Nehmen wir nun einen Anwendungsfall für die Funktionsanwendungsverknüpfung an. Gegeben eine Karte, die Ganzzahlen Zeichenfolgen zuordnet:

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

Wooop, es gibt bereits ein weiteres Auftreten einer seltsamen Interpunktion. Der Bindestrich und die Zeichen größer als, die einem Rechtspfeil ähneln , sind ein Operator, der a erzeugt Tuple2. Es gibt also auch keinen Unterschied im Ergebnis des Schreibens (1, "Eins")oder 1 -> "Eins"nur, dass letzteres leichter zu lesen ist, insbesondere in einer Liste von Tupeln wie dem Kartenbeispiel. Das ->ist keine Magie, es ist, wie einige andere Operatoren, verfügbar, da Sie alle impliziten Konvertierungen im Objekt scala.Predefim Gültigkeitsbereich haben. Die Konvertierung, die hier stattfindet, ist

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

Wo ArrowAssochat die ->Methode, die die erstellt Tuple2. Somit 1 -> "Eins"ist der Anruf aktuell Predef.any2ArrowAssoc(1).->("Eins"). OK. Nun zurück zur ursprünglichen Frage mit dem Unterstrich:

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

Der Unterstrich hier verkürzt den folgenden äquivalenten Code:

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

Beachten Sie, dass die mapMethode einer Map das Tupel aus Schlüssel und Wert an das Funktionsargument übergibt. Da wir nur an den Werten (den Zeichenfolgen) interessiert sind, extrahieren wir sie mit der _2Methode im Tupel.

0__
quelle
+1 Ich hatte Probleme beim Versuch, die ->Methode zu verstehen , aber Ihr Satz "Es gibt also auch keinen Unterschied im Ergebnis des Schreibens (1, "Eins")oder 1 -> "Eins"" hat mir geholfen, die Syntax und ihre Verwendung zu verstehen.
Jesse Webb
Zu Ihrer
Information,
15

Als Ergänzung zu den brillanten Antworten von Daniel und 0__ muss ich sagen, dass Scala Unicode- Analoga für einige der Symbole versteht, also statt

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

man darf schreiben

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

In Bezug darauf ::gibt es einen weiteren Stackoverflow- Eintrag, der den ::Fall abdeckt . Kurz gesagt, es wird verwendet, um Listsdurch ' consing ' zu konstruieren und eine " werden. Es ist sowohl eine Klasse, die eine konsistente Liste darstellt als auch als Extraktor verwendet werden kann, aber am häufigsten ist es eine Methode in einer Liste. Als Pablo Fernandez weist darauf hin, da es in einem Doppelpunkt endet, ist es richtig assoziativ , den Empfänger der Methodenaufruf Sinn ist auf der rechten Seite , und das Argument für den der Betreiber überlassen. Auf diese Weise können Sie die Nachteile elegant ausdrücken, indem Sie einer vorhandenen Liste ein neues Kopfelement voranstellen :

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

Dies entspricht

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

Die Verwendung als Extraktionsobjekt ist wie folgt:

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"

Dies sieht hier wie ein Operator aus, ist aber wirklich nur eine andere (besser lesbare) Schreibweise

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

Weitere Informationen zu Extraktoren finden Sie in diesem Beitrag .

0__
quelle
9

<=ist genau so, als würden Sie es "lesen": "kleiner als oder gleich". Es ist also ein mathematischer Operator in der Liste von <(ist kleiner als?), >(Ist größer als?), ==(Ist gleich?), !=(Ist nicht gleich?), <=(Ist kleiner als oder gleich?) Und >=(ist größer als ?) oder gleich?).

Dies darf nicht mit einem doppelten Pfeil nach rechts verwechselt werden , der verwendet wird, um die Argumentliste vom Hauptteil einer Funktion zu trennen und um die Testbedingung beim Mustervergleich (einen Block) vom Hauptteil zu trennen, der ausgeführt wird, wenn eine Übereinstimmung auftritt . Sie können ein Beispiel dafür in meinen beiden vorherigen Antworten sehen. Erstens verwendet die Funktion:=>case

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

Dies wird bereits abgekürzt, da die Typen weggelassen werden. Die folgende Funktion wäre

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

und die Verwendung des Mustervergleichs:

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__
quelle
4
Um diese Verwirrung zu vermeiden, habe ich beschlossen, die Unicode-Zeichen für den rechten Doppelpfeil (\ U21D2), den einzelnen rechten "Karten" -Pfeil (\ U2192) und den linken Einzelpfeil "in" (\ U2190) zu verwenden. Scala unterstützt dies, aber ich war ein wenig skeptisch, bis ich es eine Weile versuchte. Schauen Sie einfach nach, wie Sie diese Codepunkte an eine praktische Tastenkombination auf Ihrem System binden. Es war wirklich einfach für OS X.
Connor Doyle
5

Ich halte eine moderne IDE für entscheidend für das Verständnis großer Scala-Projekte. Da diese Operatoren auch Methoden sind, klicke ich in der Idee einfach mit gedrückter Ctrl-Taste oder Strg-B in die Definitionen.

Sie können bei gedrückter Ctrl-Taste nach rechts in einen Cons-Operator (: :) klicken und am Scala Javadoc landen und sagen: "Fügt ein Element am Anfang dieser Liste hinzu." Bei benutzerdefinierten Operatoren wird dies noch kritischer, da sie in schwer zu findenden Implikaten definiert werden können. Ihre IDE weiß, wo das Implizit definiert wurde.

nairbv
quelle
4

Nur zu den anderen ausgezeichneten Antworten hinzufügen. Scala bietet zwei häufig kritisierte symbolische Operatoren an, /:( foldLeft) und :\( foldRight) Operatoren, von denen der erste rechtsassoziativ ist. Die folgenden drei Aussagen sind also gleichbedeutend:

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

Wie sind diese drei:

( 1 to 100 ).foldRight( 0, _+_ )
( 1 to 100 ).:\( 0 )( _+_ )
( ( 1 to 100 ) :\ 0 )( _+_ )
Herr MT
quelle
2

Scala erbt die meisten Rechenoperatoren von Java . Dies umfasst bitweises oder |(Einzelpipe-Zeichen), bitweises und &bitweises Exklusiv-oder ^sowie logisches (Boolesches) oder ||(zwei Pipe-Zeichen) und logisches und &&. Interessanterweise können Sie die Einzelzeichenoperatoren verwenden boolean, sodass die logischen Java-Operatoren völlig redundant sind:

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

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

Wie in einem anderen Beitrag erwähnt, werden Anrufe, die mit einem Gleichheitszeichen enden =, durch eine Neuzuweisung aufgelöst (wenn eine Methode mit diesem Namen nicht existiert!):

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

Diese 'doppelte Überprüfung' ermöglicht es, eine veränderbare Sammlung leicht gegen eine unveränderliche Sammlung auszutauschen:

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__
quelle
4
PS Es gibt einen Unterschied zwischen der Verwendung der Einzel- und Doppelzeichenoperatoren für Boolesche Werte - der erstere ist eifrig (alle Begriffe werden ausgewertet), der letztere wird vorzeitig beendet, wenn der resultierende Boolesche Wert bekannt ist: true | { println( "Icke" ); true }⇒ druckt! true || { println( "Icke" ); true }⇒ druckt nicht !
0__