O que significam todos os operadores simbólicos da Scala?

402

A sintaxe do Scala possui muitos símbolos. Como é difícil encontrar esses tipos de nomes usando os mecanismos de pesquisa, uma lista abrangente deles seria útil.

Quais são todos os símbolos em Scala e o que cada um deles faz?

Em particular, eu gostaria de saber sobre ->, ||=, ++=, <=, _._, ::, e :+=.

0__
fonte
4
e o índice da Staircase 1ª edição, em >> artima.com/pins1ed/book-index.html#indexanchor
Gene T
2
Relacionado: caracteres do operador x caracteres alfanuméricos: stackoverflow.com/questions/7656937/…
Luigi Plinge
11
Além disso, se houver "operadores" (que são principalmente métodos, com alguns nomes de classe usados ​​em infix) que você não encontra no scalex ou no livro de escadas, por exemplo, "!!", as fontes prováveis ​​são os scaladocs para akka, scalaz e sbt
Gene T
exemplo de nome de classe usado infix (em alemão) >> raichoo.blogspot.com/2010/06/spass-mit-scala-infixtypen.html
Gene T
sobre a questão de filtrar pelos motores de busca, symbolhound.com também é uma boa alternativa
Patrick Refondini

Respostas:

526

Divido os operadores, para fins de ensino, em quatro categorias :

  • Palavras-chave / símbolos reservados
  • Métodos importados automaticamente
  • Métodos comuns
  • Açúcares sintáticos / composição

É uma sorte, então, que a maioria das categorias esteja representada na pergunta:

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

O significado exato da maioria desses métodos depende da classe que os está definindo. Por exemplo, <=on Intsignifica "menor ou igual a" . O primeiro, ->vou dar como exemplo abaixo. ::é provavelmente o método definido em List(embora possa ser o objeto com o mesmo nome) e :+=provavelmente é o método definido em várias Bufferclasses.

Então, vamos vê-los.

Palavras-chave / símbolos reservados

Existem alguns símbolos em Scala que são especiais. Dois deles são considerados palavras-chave adequadas, enquanto outros são apenas "reservados". Eles são:

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

Tudo isso faz parte do idioma e, como tal, pode ser encontrado em qualquer texto que descreva adequadamente o idioma, como o próprio Scala Specification (PDF).

O último, o sublinhado, merece uma descrição especial, porque é amplamente utilizada e tem muitos significados diferentes. Aqui está uma amostra:

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

Eu provavelmente esqueci outro significado, no entanto.

Métodos importados automaticamente

Portanto, se você não encontrou o símbolo que está procurando na lista acima, ele deve ser um método ou parte dele. Mas, com frequência, você verá algum símbolo e a documentação da classe não terá esse método. Quando isso acontece, você está visualizando uma composição de um ou mais métodos com outra coisa ou o método foi importado para o escopo ou está disponível por meio de uma conversão implícita importada.

Eles ainda podem ser encontrados no ScalaDoc : você apenas precisa saber onde procurá-los. Ou, na sua falta, veja o índice (atualmente quebrado em 2.9.1, mas disponível todas as noites).

Todo código Scala tem três importações automáticas:

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

Os dois primeiros disponibilizam apenas classes e objetos singleton. O terceiro contém todas as conversões implícitas e métodos importados, pois Predefé um objeto em si.

Olhando para dentro, Predefmostre rapidamente alguns símbolos:

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

Qualquer outro símbolo será disponibilizado através de uma conversão implícita . Basta olhar para os métodos marcados com implicitque recebem, como parâmetro, um objeto do tipo que está recebendo o método. Por exemplo:

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

No caso acima, ->é definido na classe ArrowAssocatravés do método any2ArrowAssocque leva um objeto do tipo A, em que Aé um parâmetro do tipo ilimitado para o mesmo método.

Métodos comuns

Portanto, muitos símbolos são simplesmente métodos em uma classe. Por exemplo, se você fizer

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

Você encontrará o método ++no ScalaDoc for List . No entanto, há uma convenção que você deve estar ciente ao procurar métodos. Os métodos que terminam em dois pontos ( :) se ligam à direita em vez da esquerda. Em outras palavras, enquanto a chamada do método acima é equivalente a:

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

Se eu tivesse, 1 :: List(2, 3)isso seria equivalente a:

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

Então, você precisa olhar para o tipo encontrado à direita ao procurar métodos que terminam em dois pontos. Considere, por exemplo:

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

O primeiro método ( +:) se liga à direita e é encontrado em List. O segundo método ( :+) é apenas um método normal e se liga à esquerda - novamente, ativado List.

Açúcares sintáticos / composição

Então, aqui estão alguns açúcares sintáticos que podem ocultar um 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

O último é interessante, porque qualquer método simbólico pode ser combinado para formar um método semelhante a uma atribuição dessa maneira.

E, claro, existem várias combinações que podem aparecer no 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
fonte
11
Você quis dizer em val c = ex(2)vez de val ex(c) = 2?
Mike Fique
3
@ MikeStay Não, eu quis dizer val ex(c) = 2.
Daniel C. Sobral
Oh, está usando a sintaxe de correspondência de padrões. Obrigado.
Mike Fique
=> também confere o status 'chamar pelo nome' quando usado entre: e digite como em y: => Int '
Stephen W. Wright
11
Talvez deva também mencionar os operadores: / e: \ realmente não intuitivos. Portanto, map.foldLeft (initialVal) é o mesmo que (initialVal: / map) -: \ é foldRight.
MT MT
24

Uma diferença (boa, IMO) entre o Scala e outros idiomas é que ele permite que você nomeie seus métodos com quase qualquer caractere.

O que você enumera não é "pontuação", mas métodos simples e simples, e, como tal, seu comportamento varia de um objeto para outro (embora existam algumas convenções).

Por exemplo, consulte a documentação do Scaladoc para obter a lista e você verá alguns dos métodos mencionados aqui.

Algumas coisas a ter em mente:

  • Na maioria das vezes a A operator+equal Bcombinação traduz a A = A operator B, como nos ||=ou ++=exemplos.

  • Os métodos que terminam :são associativos, isso significa que A :: Bé realmente B.::(A).

Você encontrará mais respostas navegando na documentação do Scala. Manter uma referência aqui duplicaria os esforços e ficaria para trás rapidamente :)

Pablo Fernandez
fonte
21

Você pode agrupá-los primeiro de acordo com alguns critérios. Neste post, explicarei apenas o caractere de sublinhado e a seta à direita.

_._contém um período. Um período no Scala sempre indica uma chamada de método . Então, à esquerda do período, você tem o receptor e à direita a mensagem (nome do método). Agora _é um símbolo especial em Scala. Existem várias postagens sobre o assunto, por exemplo, esta entrada de blog, todos os casos de uso. Aqui está um atalho de função anônima , que é um atalho para uma função que pega um argumento e invoca o método _nele. Agora _não é um método válido, então certamente você estava vendo _._1ou algo semelhante, ou seja, invocando o método _._1no argumento da função. _1a _22são os métodos de tuplos que extraem um elemento particular de um tuplo. Exemplo:

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

Agora vamos assumir um caso de uso para o atalho do aplicativo de funções. Dado um mapa que mapeia números inteiros para strings:

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

Wooop, já existe outra ocorrência de uma pontuação estranha. O hífen e caracteres maiores que, que se assemelham a uma seta à direita , são um operador que produz a Tuple2. Portanto, não há diferença no resultado de escrever um (1, "Eins")ou outro 1 -> "Eins", apenas que o último é mais fácil de ler, especialmente em uma lista de tuplas como o exemplo do mapa. O ->há mágica, é, como alguns outros operadores, disponível porque você tem todos os implícitos conversões em objeto scala.Predefno espaço. A conversão que ocorre aqui é

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

Onde ArrowAssoctem o ->método que cria o Tuple2. Assim 1 -> "Eins"é real a chamada Predef.any2ArrowAssoc(1).->("Eins"). Está bem. Agora, de volta à pergunta original com o caractere sublinhado:

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

O sublinhado aqui reduz o seguinte código equivalente:

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

Observe que o mapmétodo de um mapa passa a tupla de chave e valor para o argumento da função. Como estamos interessados ​​apenas nos valores (as strings), os extraímos com o _2método na tupla.

0__
fonte
+1 Eu estava com problemas para tentar entender o ->método, mas sua frase "Portanto, não há diferença no resultado da escrita de um (1, "Eins")ou outro 1 -> "Eins"" me ajudou a compreender a sintaxe e seu uso.
Jesse Webb
fyi seu link de entrada do blog está morto
still_learning 17/04
15

Como complemento às brilhantes respostas de Daniel e 0__, devo dizer que Scala entende os análogos Unicode para alguns dos símbolos; portanto, em vez de

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

alguém pode escrever

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

A respeito, ::há outra entrada Stackoverflow que cobre o ::caso. Em resumo, é usado para construir Lists' consing ' um elemento principal e uma lista final . É uma classe que representa uma lista cons'ed e que pode ser usada como extrator, mas o mais comum é um método em uma lista. Como Pablo Fernandez aponta, como termina em dois pontos, é associativo à direita , o que significa que o receptor da chamada de método está à direita e o argumento à esquerda do operador. Dessa forma, você pode elegantemente expressar a consing como prepending um novo elemento de cabeça para uma lista existente:

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

Isso é equivalente a

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

O uso como objeto extrator é o seguinte:

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"

Parece um operador aqui, mas é realmente apenas outra maneira (mais legível) de escrever

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

Você pode ler mais sobre extratores neste post .

0__
fonte
9

<=é exatamente como você "leria": 'menor ou igual'. Portanto, é um operador matemático, na lista de <(é menor que?), >(É maior que?), ==(Igual?), !=(Não é igual?), <=(É menor que ou igual?) E >=(é maior que ou igual?).

Isso não deve ser confundido com o =>que é uma espécie de seta dupla à direita , usada para separar a lista de argumentos do corpo de uma função e para separar a condição de teste na correspondência de padrões (um casebloco) do corpo executado quando ocorre uma correspondência . Você pode ver um exemplo disso nas minhas duas respostas anteriores. Primeiro, a função usa:

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

que já é abreviado quando os tipos são omitidos. A função a seguir seria

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

e o uso de correspondência de padrões:

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__
fonte
4
Para evitar essa confusão, decidi começar a usar os caracteres unicode para a seta dupla à direita (\ U21D2), a seta "mapas" à direita (\ U2192) e a seta "in" à esquerda (\ U2190). Scala apóia isso, mas fiquei um pouco cético até tentar por um tempo. Veja como vincular esses pontos de código a uma combinação conveniente de teclas em seu sistema. Foi muito fácil no OS X.
Connor Doyle
5

Considero que um IDE moderno é fundamental para entender grandes projetos de escala. Como esses operadores também são métodos, na idéia, apenas clico ou clico-b nas definições.

Você pode clicar com o botão direito do mouse em um operador contras (: :) e terminar no scala javadoc dizendo "Adiciona um elemento no início desta lista". Em operadores definidos pelo usuário, isso se torna ainda mais crítico, pois eles podem ser definidos em implícitos difíceis de encontrar ... seu IDE sabe onde o implícito foi definido.

nairbv
fonte
4

Apenas adicionando às outras excelentes respostas. Scala oferece dois operadores simbólicos frequentemente criticados, /:( foldLeft) e :\( foldRight), sendo o primeiro associativo. Portanto, as três instruções a seguir são equivalentes:

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

Como são esses três:

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

O Scala herda a maioria dos operadores aritméticos de Java . Isso inclui bit a bit ou |(caractere de pipe único), bit a bit e &, bit a bit exclusivo ou ^, além de lógico (booleano) ou ||(dois caracteres de pipe) e lógico e &&. Curiosamente, você pode usar os operadores de caractere único boolean, para que os operadores lógicos java'ish sejam totalmente redundantes:

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

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

Como apontado em outro post, as chamadas que terminam em sinal de igual =são resolvidas (se um método com esse nome não existir!) Por uma reatribuição:

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

Essa 'verificação dupla' possibilita a troca fácil de um mutável por uma coleção imutável:

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__
fonte
4
PS Existe uma diferença entre o uso de operadores de caracteres simples x caracteres duplos nos booleanos - o primeiro está ansioso (todos os termos são avaliados), o último termina mais cedo se o booleano resultante for conhecido: true | { println( "Icke" ); true }⇒ imprime! true || { println( "Icke" ); true }não imprime!
0__