¿Qué significan todos los operadores simbólicos de Scala?

402

La sintaxis de Scala tiene muchos símbolos. Dado que este tipo de nombres son difíciles de encontrar utilizando los motores de búsqueda, sería útil una lista completa de ellos.

¿Cuáles son todos los símbolos en Scala y qué hace cada uno de ellos?

En particular, me gustaría saber acerca de ->, ||=, ++=, <=, _._, ::, y :+=.

0__
fuente
44
y el índice de Staircase 1st edition, en >> artima.com/pins1ed/book-index.html#indexanchor
Gene T
2
Relacionado: caracteres de operador vs caracteres alfanuméricos: stackoverflow.com/questions/7656937/…
Luigi Plinge
1
Además, si hay "operadores" (que son en su mayoría métodos, con algunos nombres de clase utilizados infijo) que no puede encontrar en scalex o en el libro de la escalera, por ejemplo, "!!", las fuentes probables son los scaladocs para akka, scalaz y sbt
Gene T
ejemplo de nombre de clase utilizado infijo (en alemán) >> raichoo.blogspot.com/2010/06/spass-mit-scala-infixtypen.html
Gene T
Con respecto al tema del filtrado por motores de búsqueda, symbolhound.com también es una buena alternativa
Patrick Refondini, el

Respuestas:

526

Divido a los operadores, con el propósito de enseñar, en cuatro categorías :

  • Palabras clave / símbolos reservados
  • Métodos importados automáticamente
  • Métodos comunes
  • Azúcares sintácticos / composición

Es una suerte, entonces, que la mayoría de las categorías estén representadas en la pregunta:

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

El significado exacto de la mayoría de estos métodos depende de la clase que los define. Por ejemplo, <=on Intsignifica "menor o igual que" . El primero, ->daré como ejemplo a continuación. ::es probablemente el método definido en List(aunque podría ser el objeto del mismo nombre), y :+=es probablemente el método definido en varias Bufferclases.

Entonces, vamos a verlos.

Palabras clave / símbolos reservados

Hay algunos símbolos en Scala que son especiales. Dos de ellas se consideran palabras clave adecuadas, mientras que otras simplemente están "reservadas". Son:

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

Todos estos son parte del lenguaje y, como tal, se pueden encontrar en cualquier texto que describa adecuadamente el idioma, como la propia Especificación de Scala (PDF).

El último, el guión bajo, merece una descripción especial, porque es muy utilizado y tiene muchos significados diferentes. Aquí hay una muestra:

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

Sin embargo, probablemente olvidé algún otro significado.

Métodos importados automáticamente

Por lo tanto, si no encontró el símbolo que busca en la lista anterior, debe ser un método o parte de uno. Pero, a menudo, verá algún símbolo y la documentación de la clase no tendrá ese método. Cuando esto sucede, está viendo una composición de uno o más métodos con otra cosa, o el método se ha importado al alcance o está disponible a través de una conversión implícita importada.

Estos todavía se pueden encontrar en ScalaDoc : sólo hay que saber dónde buscar para ellos. O, en su defecto, mire el índice (actualmente roto en 2.9.1, pero disponible en la noche).

Cada código Scala tiene tres importaciones automáticas:

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

Los dos primeros solo hacen que las clases y los objetos singleton estén disponibles. El tercero contiene todas las conversiones implícitas y métodos importados, ya que Predefes un objeto en sí mismo.

Mirando hacia dentro Predefrápidamente muestra algunos símbolos:

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

Cualquier otro símbolo estará disponible a través de una conversión implícita . Solo mire los métodos etiquetados con implicitesa recepción, como parámetro, un objeto de tipo que está recibiendo el método. Por ejemplo:

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

En el caso anterior, ->se define en la clase a ArrowAssoctravés del método any2ArrowAssocque toma un objeto de tipo A, donde Aes un parámetro de tipo ilimitado para el mismo método.

Métodos comunes

Entonces, muchos símbolos son simplemente métodos en una clase. Por ejemplo, si lo haces

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

Encontrará el método ++directamente en ScalaDoc for List . Sin embargo, hay una convención que debe tener en cuenta al buscar métodos. Los métodos que terminan en dos puntos ( :) se unen a la derecha en lugar de a la izquierda. En otras palabras, mientras que la llamada al método anterior es equivalente a:

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

Si tuviera, en cambio 1 :: List(2, 3), eso sería equivalente a:

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

Por lo tanto, debe buscar el tipo que se encuentra a la derecha cuando busque métodos que terminen en dos puntos. Considere, por ejemplo:

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

El primer método ( +:) se une a la derecha y se encuentra en List. El segundo método ( :+) es solo un método normal y se une a la izquierda, nuevamente, encendido List.

Azúcares sintácticos / composición

Entonces, aquí hay algunos azúcares sintácticos que pueden ocultar un método:

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

El último es interesante, porque cualquier método simbólico se puede combinar para formar un método de asignación de esa manera.

Y, por supuesto, hay varias combinaciones que pueden aparecer en el código:

(_+_) // 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
fuente
1
¿Quiso decir en val c = ex(2)lugar de val ex(c) = 2?
Mike Stay
3
@ MikeStay No, quise decir val ex(c) = 2.
Daniel C. Sobral
Oh, está usando la sintaxis de coincidencia de patrones. Gracias.
Mike Stay
=> también confiere el estado 'llamar por nombre' cuando se usa entre: y escriba como en y: => Int '
Stephen W. Wright
1
Tal vez uno también debería mencionar los operadores: / y: \ realmente poco intuitivos. Entonces map.foldLeft (initialVal) es lo mismo que (initialVal: / map) -: \ is foldRight en su lugar.
Sr. MT
24

Una (buena, IMO) diferencia entre Scala y otros idiomas es que te permite nombrar tus métodos con casi cualquier carácter.

Lo que enumera no es "puntuación" sino métodos simples y simples, y como tal su comportamiento varía de un objeto a otro (aunque hay algunas convenciones).

Por ejemplo, consulte la documentación de Scaladoc para Lista , y verá algunos de los métodos que mencionó aquí.

Algunas cosas para tener en mente:

  • La mayoría de las veces la A operator+equal Bcombinación se traduce en A = A operator B, como en los ejemplos ||=o ++=.

  • Los métodos que terminan en :son asociativos correctos, esto significa que A :: Bes realmente B.::(A).

Encontrarás la mayoría de las respuestas navegando por la documentación de Scala. Mantener una referencia aquí duplicaría los esfuerzos, y se retrasaría rápidamente :)

Pablo Fernández
fuente
21

Puede agruparlos primero de acuerdo con algunos criterios. En esta publicación solo explicaré el carácter de subrayado y la flecha hacia la derecha.

_._contiene un punto Un punto en Scala siempre indica una llamada al método . A la izquierda del período que tiene el receptor, y a la derecha del mensaje (nombre del método). Ahora _es un símbolo especial en Scala. Hay varias publicaciones al respecto, por ejemplo, esta entrada de blog, todos los casos de uso. Aquí se trata de un atajo de función anónima , es decir, un atajo para una función que toma un argumento e invoca el método _en él. Ahora _no es un método válido, por lo que seguramente estaba viendo _._1o algo similar, es decir, invocar un método _._1en el argumento de la función. _1a _22son los métodos de tuplas que extraen un elemento particular de una tupla. Ejemplo:

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

Ahora supongamos un caso de uso para el acceso directo de la aplicación de función. Dado un mapa que asigna enteros a cadenas:

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

Wooop, ya hay otra ocurrencia de una puntuación extraña. El guión y los caracteres mayores que, que se asemejan a una flecha a la derecha , es un operador que produce un Tuple2. Por lo tanto, no hay diferencia en el resultado de escribir (1, "Eins")o 1 -> "Eins", solo que este último es más fácil de leer, especialmente en una lista de tuplas como el ejemplo del mapa. No ->es magia, está, como algunos otros operadores, disponible porque tiene todas las conversiones implícitas en el objeto scala.Predefen su alcance. La conversión que tiene lugar aquí es

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

Donde ArrowAssoctiene el ->método que crea el Tuple2. Así 1 -> "Eins"es real la llamada Predef.any2ArrowAssoc(1).->("Eins"). Okay. Ahora volvamos a la pregunta original con el carácter de subrayado:

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

El guión bajo aquí acorta el siguiente código equivalente:

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

Tenga en cuenta que el mapmétodo de un Mapa pasa la tupla de clave y valor al argumento de la función. Como solo estamos interesados ​​en los valores (las cadenas), los extraemos con el _2método en la tupla.

0__
fuente
1 Estaba teniendo problemas para tratar de entender el ->método, pero su frase "Así que no hay diferencia en el resultado de la escritura, ya sea (1, "Eins")o 1 -> "Eins"" me ayudó a comprender la sintaxis y su uso.
Jesse Webb
FYI su enlace de entrada de blog está muerto
still_learning
15

Como una adición a las brillantes respuestas de Daniel y 0__, tengo que decir que Scala entiende los análogos de Unicode para algunos de los símbolos, así que en lugar de

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

uno puede escribir

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

En cuanto a que ::hay otra entrada de Stackoverflow que cubre el ::caso. En resumen, se utiliza para construir Lists' consing ' un elemento principal y una lista de cola. Es una clase que representa una lista completa y que se puede usar como extractor, pero lo más común es método en una lista. Como señala Pablo Fernández, ya que termina en dos puntos, es asociativo a la derecha , lo que significa que el receptor de la llamada al método está a la derecha y el argumento a la izquierda del operador. De esa manera se puede expresar con elegancia el Consing como anteponiendo un nuevo elemento de la cabeza a una lista existente:

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

Esto es equivalente a

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

El uso como objeto extractor es el siguiente:

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"

Esto parece un operador aquí, pero en realidad es solo otra forma (más legible) de escribir

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

Puedes leer más sobre extractores en esta publicación .

0__
fuente
9

<=es exactamente como lo "leerías": "menor o igual que". Entonces, es un operador matemático, en la lista de <(¿es menor que?), >(¿Es mayor que?), ==(¿Igual?), !=(¿No es igual?), <=(¿Es menor o igual?), Y>= (es mayor que ¿o igual?).

Esto no debe confundirse con =>cuál es una especie de doble flecha hacia la derecha , utilizada para separar la lista de argumentos del cuerpo de una función y para separar la condición de prueba en la coincidencia de patrones (un casebloque) del cuerpo ejecutado cuando se produce una coincidencia . Puedes ver un ejemplo de esto en mis dos respuestas anteriores. Primero, el uso de la función:

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

que ya se abrevia a medida que se omiten los tipos. La siguiente función sería

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

y el uso de coincidencia de patrones:

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__
fuente
44
Evitar esta confusión es la razón por la que decidí comenzar a utilizar los caracteres unicode para la flecha doble derecha (\ U21D2), la flecha única de "mapas" de la derecha (\ U2192) y la flecha simple "de" izquierda (\ U2190). Scala lo admite, pero estaba un poco escéptico hasta que lo probé por un tiempo. Simplemente busque cómo vincular estos puntos de código a una combinación de teclas conveniente en su sistema. Fue realmente fácil en OS X.
Connor Doyle
5

Considero que un IDE moderno es crítico para comprender proyectos de gran escala. Como estos operadores también son métodos, en intellij idea simplemente control-click o control-b en las definiciones.

Puede hacer clic con el botón derecho del mouse en un operador de contras (: :) y terminar en el scala javadoc diciendo "Agrega un elemento al comienzo de esta lista". En los operadores definidos por el usuario, esto se vuelve aún más crítico, ya que podrían definirse en dificultades difíciles de encontrar ... su IDE sabe dónde se definió lo implícito.

nairbv
fuente
4

Solo agregando a las otras excelentes respuestas. Scala ofrece dos operadores simbólicos a menudo criticados, /:( foldLeft) y :\( foldRight) operadores, el primero es derecho-asociativo. Entonces las siguientes tres declaraciones son equivalentes:

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

Como son estos tres:

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

Scala hereda la mayoría de los operadores aritméticos de Java . Esto incluye bitwise-or |(carácter de canal único), bitwise-y &, bitwise-exclusive-or ^, así como lógico (booleano) o ||(dos caracteres de canalización) y logical-and &&. Curiosamente, puede usar los operadores de un solo carácter boolean, por lo que los operadores lógicos java'ish son totalmente redundantes:

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

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

Como se señaló en otra publicación, las llamadas que terminan en un signo de igual =se resuelven (si no existe un método con ese nombre) mediante una reasignación:

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

Este 'doble control' hace posible intercambiar fácilmente un mutable por una colección inmutable:

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__
fuente
44
PD: Hay una diferencia entre usar operadores de caracteres simples y dobles en booleanos: el primero está ansioso (se evalúan todos los términos), el último termina antes si se conoce el booleano resultante: true | { println( "Icke" ); true }⇒ imprime! true || { println( "Icke" ); true }no imprime!
0__