Co oznaczają wszystkie symboliczne operatory Scali?

402

Składnia Scala ma wiele symboli. Ponieważ tego rodzaju nazwy są trudne do znalezienia za pomocą wyszukiwarek, przydatna byłaby ich pełna lista.

Jakie są wszystkie symbole w Scali i co robi każdy z nich?

W szczególności chciałbym wiedzieć ->, ||=, ++=, <=, _._, ::, i :+=.

0__
źródło
4
oraz indeks pierwszej edycji Staircase, na >> artima.com/pins1ed/book-index.html#indexanchor
Gene T
2
Powiązane: operator znaków alfanumerycznych znaków vs: stackoverflow.com/questions/7656937/...
Luigi Plinge
1
także, jeśli istnieją „operatory” (które są przeważnie metodami, z kilkoma nazwami klas używanymi w poprawkach), których nie można znaleźć w skalix lub książce schodowej, np. „!!”, prawdopodobnie źródłem są scaladoki dla akka, scalaz i sbt
Gene T
przykład użytej nazwy klasy infix
Gene T
jeśli chodzi o kwestię filtrowania przez wyszukiwarki, symbolhound.com jest również dobrą alternatywą
Patrick Refondini

Odpowiedzi:

526

Do celów nauczania dzielę operatorów na cztery kategorie :

  • Słowa kluczowe / symbole zastrzeżone
  • Metody importowane automatycznie
  • Wspólne metody
  • Cukry syntaktyczne / skład

Na szczęście zatem większość kategorii jest reprezentowana w pytaniu:

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

Dokładne znaczenie większości tych metod zależy od klasy, która je definiuje. Na przykład <=on Intoznacza „mniejszy lub równy” . Pierwszy, ->podam jako przykład poniżej. ::jest prawdopodobnie metodą zdefiniowaną na List(choć może to być obiekt o tej samej nazwie) i :+=prawdopodobnie jest metodą zdefiniowaną dla różnych Bufferklas.

Zobaczmy ich.

Słowa kluczowe / symbole zastrzeżone

W Scali jest kilka symboli, które są wyjątkowe. Dwa z nich są uważane za właściwe słowa kluczowe, a inne są po prostu „zastrzeżone”. Oni są:

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

Wszystkie są częścią języka i jako takie można je znaleźć w dowolnym tekście, który prawidłowo opisuje ten język, takim jak sama specyfikacja Scala (PDF).

Ostatni, podkreślenie, zasługuje na specjalny opis, ponieważ jest tak szeroko stosowany i ma tak wiele różnych znaczeń. Oto próbka:

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

Prawdopodobnie zapomniałem jednak o innym znaczeniu.

Metody importowane automatycznie

Tak więc, jeśli nie znalazłeś szukanego symbolu na powyższej liście, to musi to być metoda lub jej część. Ale często zobaczysz jakiś symbol, a dokumentacja dla klasy nie będzie miała tej metody. Kiedy tak się dzieje, albo patrzysz na kompozycję jednej lub więcej metod z czymś innym, albo metoda została zaimportowana do zakresu lub jest dostępna poprzez zaimportowaną niejawną konwersję.

nadal można znaleźć na ScalaDoc : po prostu trzeba wiedzieć gdzie ich szukać. Lub, jeśli to nie wystarczy, spójrz na indeks (obecnie uszkodzony w wersji 2.9.1, ale dostępny w nocy).

Każdy kod Scala ma trzy automatyczne importy:

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

Pierwsze dwa udostępniają tylko klasy i obiekty singletonowe. Trzeci zawiera wszystkie niejawne konwersje i importowane metody, ponieważ Predefjest to sam obiekt.

Zaglądając do środka Predefszybko pokaż kilka symboli:

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

Każdy inny symbol zostanie udostępniony w drodze domniemanej konwersji . Wystarczy spojrzeć na metody oznaczone implicittym parametrem i otrzymać jako parametr obiekt typu odbierający metodę. Na przykład:

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

W powyższym przypadku ->jest definiowany w klasie ArrowAssocza pomocą metody, any2ArrowAssocktóra przenosi obiekt typu A, gdzie Ajest parametr typu nieograniczony dla tej samej metody.

Wspólne metody

Tak więc wiele symboli jest po prostu metodami w klasie. Na przykład jeśli tak

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

Metodę znajdziesz ++bezpośrednio w ScalaDoc for List . Istnieje jednak jedna konwencja, o której należy pamiętać podczas wyszukiwania metod. Metody kończące się na dwukropek ( :) wiążą się z prawej zamiast lewej. Innymi słowy, podczas gdy powyższe wywołanie metody jest równoważne z:

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

Gdybym miał, 1 :: List(2, 3)to byłoby to równoważne z:

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

Musisz więc spojrzeć na typ znaleziony po prawej stronie , szukając metod kończących się dwukropkiem. Zastanów się na przykład:

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

Pierwsza metoda ( +:) wiąże się po prawej stronie i znajduje się na List. Druga metoda ( :+) jest po prostu normalną metodą i wiąże się w lewo - ponownie, dalej List.

Cukry syntaktyczne / skład

Oto kilka cukrów składniowych, które mogą ukryć metodę:

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

Ostatni jest interesujący, ponieważ dowolną metodę symboliczną można połączyć, aby w ten sposób utworzyć metodę podobną do przypisania.

I oczywiście w kodzie mogą występować różne kombinacje:

(_+_) // 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
źródło
1
Miałeś na myśli val c = ex(2)zamiast val ex(c) = 2?
Mike Stay
3
@MikeStay Nie, miałem na myśli val ex(c) = 2.
Daniel C. Sobral
Och, używa składni dopasowania wzorca. Dzięki.
Mike Stay
=> nadaje także status „call by name”, gdy jest używany między: i wpisz jak w y: => Int ”
Stephen W. Wright,
1
Być może należy również wspomnieć o: / i: \ naprawdę nieintuicyjnych operatorach. Zatem map.foldLeft (initialVal) jest taki sam jak (initialVal: / map) -: \ zamiast tego jest foldRight.
Pan MT
24

Jedną (dobrą, IMO) różnicą między językiem Scala a innymi językami jest to, że pozwala nazwać swoje metody niemal każdą postacią.

To, co wyliczasz, nie jest „interpunkcją”, ale prostymi i prostymi metodami i jako takie ich zachowanie różni się w zależności od obiektu (choć istnieją pewne konwencje).

Na przykład sprawdź dokumentację Scaladoc w polu Lista , a zobaczysz niektóre z metod, o których tu wspomniałeś.

Kilka rzeczy, o których należy pamiętać:

  • Najczęściej A operator+equal Bkombinacja przekłada się na A = A operator B, jak w przykładach ||=lub ++=.

  • Metody kończące się na :są odpowiednio asocjacyjne, co oznacza, że A :: Btak naprawdę jest B.::(A).

Większość odpowiedzi znajdziesz w dokumentacji Scali. Zachowanie referencji podwoiłoby wysiłki i szybko się opóźniłoby :)

Pablo Fernandez
źródło
21

Możesz je najpierw pogrupować według niektórych kryteriów. W tym poście wyjaśnię tylko znak podkreślenia i strzałkę w prawo.

_._zawiera kropkę. Kropka w Scali zawsze oznacza wywołanie metody . Więc po lewej stronie kropki masz odbiornik, a po prawej wiadomość (nazwa metody). Teraz _jest specjalnym symbolem w Scali. Jest na ten temat kilka postów, na przykład ten wpis na blogu dotyczy wszystkich przypadków użycia. Tutaj jest to anonimowy skrót funkcji , czyli skrót do funkcji, która pobiera jeden argument i wywołuje na nim metodę _. Teraz _nie jest poprawną metodą, więc z pewnością widziałeś _._1lub coś podobnego, to znaczy wywoływanie metody _._1w argumencie funkcji. _1na _22są metody krotki tłoczni konkretny element krotki. Przykład:

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

Załóżmy teraz przypadek użycia skrótu do aplikacji funkcji. Biorąc pod uwagę mapę, która mapuje liczby całkowite na ciągi:

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

Ooooo, już zdarza się dziwne interpunkcja. Łącznik i znaki większe niż, które przypominają prawą strzałkę , są operatorem, który tworzy znak Tuple2. Więc nie ma różnicy w wyniku pisania albo, (1, "Eins")albo 1 -> "Eins"tylko to drugie jest łatwiejsze do odczytania, szczególnie na liście krotek takich jak przykład mapy. To ->nie jest magia, jest, podobnie jak kilka innych operatorów, dostępne, ponieważ masz wszystkie ukryte konwersje w obiekcie scala.Predefw zakresie. Następuje tutaj konwersja

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

Gdzie ArrowAssocjest ->metoda, która tworzy Tuple2. Tak 1 -> "Eins"jest rzeczywiste połączenie Predef.any2ArrowAssoc(1).->("Eins"). Ok. Wróćmy do pierwotnego pytania ze znakiem podkreślenia:

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

Podkreślenie tutaj skraca następujący równoważny kod:

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

Zauważ, że mapmetoda Map przekazuje krotkę klucza i wartości do argumentu funkcji. Ponieważ interesują nas tylko wartości (ciągi), wyodrębniamy je za pomocą _2metody na krotce.

0__
źródło
+1 Miałem problem z próbą zrozumienia ->metody, ale twoje zdanie „Więc nie ma różnicy w wyniku pisania albo ” (1, "Eins")albo 1 -> "Eins"pomogło mi zrozumieć składnię i jej użycie.
Jesse Webb
fyi Twój link do wpisu na blogu jest martwy
still_learning
15

Jako dodatek do genialnych odpowiedzi Daniela i 0__ muszę powiedzieć, że Scala rozumie analogi Unicode dla niektórych symboli, więc zamiast

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

można pisać

for (n ← 1 to 10) n % 2 match {
  case 0 ⇒ println("even")
  case 1 ⇒ println("odd")
}
mniam mniam mniam
źródło
10

Jeśli chodzi o ::inny wpis Stackoverflow, który obejmuje ::sprawę. Krótko mówiąc, służy do konstruowania Listspoprzez „ wykorzystanie ” elementu głównego i listy ogona. Jest to zarówno klasa reprezentująca rozpatrywaną listę, która może być używana jako ekstraktor, ale najczęściej jest to metoda na liście. Jak zauważa Pablo Fernandez, ponieważ kończy się dwukropkiem, ma prawy asocjatywny , co oznacza, że ​​odbiorca wywołania metody jest po prawej stronie, a argument po lewej stronie operatora. W ten sposób możesz elegancko wyrazić sumienie, przygotowując nowy element głowy do istniejącej listy:

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

Jest to równoważne z

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

Zastosowanie jako obiektu wyciągającego jest następujące:

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"

Wygląda to jak operator, ale tak naprawdę jest to po prostu inny (bardziej czytelny) sposób pisania

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

Możesz przeczytać więcej o ekstraktorach w tym poście .

0__
źródło
9

<=jest tak, jakbyś to „przeczytał”: „mniejszy lub równy”. Jest to więc operator matematyczny na liście <(jest mniejsza niż?), >(Jest większa niż?), ==(Równa się?), !=(Nie jest równa?), <=(Jest mniejsza lub równa?), I>= (jest większa niż lub równy?).

Nie należy tego mylić z czymś w =>rodzaju podwójnej prawej strzałki , używanej do oddzielenia listy argumentów od treści funkcji i do oddzielenia warunków testowych w dopasowaniu wzorca ( casebloku) od treści wykonanej w przypadku dopasowania. . Możesz zobaczyć przykład tego w moich dwóch poprzednich odpowiedziach. Po pierwsze, funkcja używa:

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

który jest już skracany, ponieważ typy są pomijane. Byłaby następująca funkcja

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

i użycie dopasowania wzoru:

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__
źródło
4
Unikając tego zamieszania, postanowiłem zacząć używać znaków Unicode dla prawej podwójnej strzałki (\ U21D2), pojedynczej prawej strzałki „mapy” (\ U2192) i lewej pojedynczej strzałki „w” (\ U2190). Scala to popiera, ale byłem trochę sceptyczny, dopóki tego nie spróbowałem. Wystarczy sprawdzić, jak powiązać te punkty kodowe z wygodną kombinacją klawiszy w systemie. Na OS X było to naprawdę łatwe
Connor Doyle,
5

Uważam, że nowoczesne IDE ma kluczowe znaczenie dla zrozumienia dużych projektów Scala. Ponieważ operatory te są również metodami, według intellij po prostu klikam z wciśniętym klawiszem Control lub Control-B w definicjach.

Możesz kliknąć z wciśniętym klawiszem Control w prawo w operatora konsoli (: :) i skończyć na scala javadoc, mówiąc: „Dodaje element na początku tej listy”. W przypadku operatorów zdefiniowanych przez użytkownika staje się to jeszcze bardziej krytyczne, ponieważ można je zdefiniować w trudnych do znalezienia implikacjach ... twoje IDE wie, gdzie zdefiniowano niejawne.

nairbv
źródło
4

Właśnie dodając do innych doskonałych odpowiedzi. Scala oferuje dwa często krytykowane operatory symboliczne, /:( foldLeft) i :\( foldRight), z których pierwszy jest prawostronny. Zatem następujące trzy stwierdzenia są równoważne:

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

Podobnie jak te trzy:

( 1 to 100 ).foldRight( 0, _+_ )
( 1 to 100 ).:\( 0 )( _+_ )
( ( 1 to 100 ) :\ 0 )( _+_ )
Pan MT
źródło
2

Scala dziedziczy większość operatorów arytmetycznych Java . Obejmuje to |znak bitowy lub (pojedynczy znak potoku), bitowy i &bitowy wyłączny lub lub ^logiczny (logiczny) lub ||(dwa znaki potoku) oraz logiczny i &&. Co ciekawe, możesz używać operatorów booleanjednoznakowych, więc javowi operatorzy logiczni są całkowicie redundantni:

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

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

Jak wskazano w innym poście, wywołania kończące się znakiem równości =są rozwiązywane (jeśli metoda o takiej nazwie nie istnieje!) Przez zmianę przypisania:

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

Ta „podwójna kontrola” umożliwia łatwą wymianę mutable na niezmienną kolekcję:

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__
źródło
4
PS Istnieje różnica między użyciem pojedynczych a podwójnych operatorów znaków na boolach - pierwszy jest chętny (wszystkie warunki są oceniane), drugi kończy się wcześniej, jeśli wynikowy boolean jest znany: true | { println( "Icke" ); true }⇒ drukuje! true || { println( "Icke" ); true }nie drukuje!
0__