Что означают все символические операторы Scala? - PullRequest
387 голосов
/ 25 октября 2011

Синтаксис Scala имеет много символов.Поскольку такого рода имена трудно найти с помощью поисковых систем, будет полезен их полный список.

Каковы все символы в Scala и что делает каждый из них?

В частности, я хотел бы знать о ->, ||=, ++=, <=, _._, :: и :+=.

Ответы [ 9 ]

507 голосов
/ 25 октября 2011

Я делю операторов для целей обучения на четыре категории :

  • Ключевые слова / зарезервированные символы
  • Автоматически импортируемые методы
  • Обычные методы
  • Синтаксические сахара / состав

К счастью, большинство категорий представлено в вопросе:

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

Точное значениеБольшинство из этих методов зависят от класса, который их определяет.Например, <= на Int означает «меньше или равно» .Первый, ->, я приведу в качестве примера ниже.::, вероятно, метод, определенный в List (хотя может быть объектом с тем же именем), а :+=, вероятно, метод, определенный в различных Buffer классах.

Итак, давайте посмотрим на них.

Ключевые слова / зарезервированные символы

В Scala есть некоторые специальные символы.Два из них считаются правильными ключевыми словами, а другие просто «зарезервированы».Это:

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

Это все часть языка , и, как таковые, их можно найти в любом тексте, который правильно описывает язык, например, Scala Specification (PDF).

Последнее, подчеркивание, заслуживает особого описания, потому что оно очень широко используется и имеет так много разных значений.Вот пример:

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

Возможно, я забыл еще какое-то значение.

Автоматически импортированные методы

Итак, если вы не нашли нужный вам символв приведенном выше списке это должен быть метод или его часть.Но часто вы увидите какой-нибудь символ, и документация для класса не будет иметь этого метода.Когда это происходит, либо вы просматриваете композицию одного или нескольких методов с чем-то другим, либо метод был импортирован в область действия или доступен через импортированное неявное преобразование.

Эти все еще могутбыть найденным на ScalaDoc : вам просто нужно знать, где их искать.Или, если это невозможно, посмотрите на index (в настоящее время не работает на 2.9.1, но доступен на ночной).

Каждый код Scala имеет три автоматических импорта:

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

Первые два делают доступными только классы и одноэлементные объекты.Третий содержит все неявные преобразования и импортированные методы, поскольку Predef является самим объектом.

Если заглянуть внутрь Predef, быстро отобразите некоторые символы:

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

Любой другой символ будет доступен через неявное преобразование .Просто посмотрите на методы, помеченные implicit, которые получают в качестве параметра объект типа, который получает метод.Например:

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

В приведенном выше случае -> определяется в классе ArrowAssoc с помощью метода any2ArrowAssoc, который принимает объект типа A,где A - это параметр неограниченного типа для того же метода.

Общие методы

Таким образом, многие символы являются просто методами в классе.Например, если вы выполните

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

, вы найдете метод ++ прямо в ScalaDoc для Список .Однако есть одно соглашение, которое вы должны знать при поиске методов.Методы, оканчивающиеся на двоеточие (:), связывают справа вместо левой.Другими словами, хотя приведенный выше вызов метода эквивалентен:

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

Если бы вместо него было 1 :: List(2, 3), это было бы эквивалентно:

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

Так что вам нужнопосмотрите на найденный тип справа при поиске методов, заканчивающихся двоеточием.Рассмотрим, например:

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

Первый метод (+:) связывается вправо и находится в List.Второй метод (:+) является обычным методом и привязывается влево - снова, на List.

Синтаксические сахара / композиция

Итак, вот несколько синтаксическихсахара, которые могут скрывать метод:

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

Последний интересен, потому что любой символический метод может быть объединен для формирования метода, подобного присвоению, таким образом.

И, конечно, в коде могут появляться различные комбинации:

(_+_) // 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.
23 голосов
/ 25 октября 2011

Одно (хорошее, IMO) различие между Scala и другими языками состоит в том, что он позволяет вам называть ваши методы практически любым символом.

То, что вы перечисляете, это не «знаки препинания», а простые и простые методы, и поэтому их поведение варьируется от одного объекта к другому (хотя существуют некоторые соглашения).

Например, проверьте документацию Scaladoc для List , и вы увидите некоторые из методов, которые вы упомянули здесь.

Некоторые вещи, которые нужно иметь в виду:

  • В большинстве случаев комбинация A operator+equal B переводится в A = A operator B, как в примерах ||= или ++=.

  • Методы, оканчивающиеся на :, являются ассоциативно правильными, это означает, что A :: B на самом деле B.::(A).

Большинство ответов вы найдете, просмотрев документацию по Scala. Сохранение ссылки здесь приведет к дублированию усилий и быстро отстанет:)

20 голосов
/ 25 октября 2011

Вы можете сгруппировать их сначала по некоторым критериям. В этом посте я просто объясню символ подчеркивания и стрелку вправо.

_._ содержит точку. Точка в Scala всегда указывает на вызов метода . Таким образом, слева от периода у вас есть получатель, а справа от него сообщение (название метода). Теперь _ - это специальный символ в Scala. Есть несколько сообщений об этом, например эта запись в блоге все варианты использования. Здесь это ярлык анонимной функции , то есть ярлык для функции, которая принимает один аргумент и вызывает для него метод _. Теперь _ не является допустимым методом, поэтому вы наверняка видели _._1 или что-то подобное, то есть вызываете метод _._1 для аргумента функции. _1 - _22 - это методы кортежей, которые извлекают определенный элемент кортежа. Пример:

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

Теперь давайте рассмотрим вариант использования ярлыка приложения функции. Дана карта, которая отображает целые числа в строки:

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

Wooop, уже есть еще один случай странной пунктуации. Дефис и символы больше чем, которые напоминают стрелку вправо , являются оператором, который выдает Tuple2. Таким образом, нет никакой разницы в результатах написания (1, "Eins") или 1 -> "Eins", только в том, что последний легче читать, особенно в списке кортежей, как пример карты. -> не волшебство, оно, как и некоторые другие операторы, доступно, потому что у вас есть все неявные преобразования в объекте scala.Predef в области видимости. Здесь происходит преобразование

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

Где ArrowAssoc имеет метод ->, который создает Tuple2. Таким образом 1 -> "Eins" является актуальным вызовом Predef.any2ArrowAssoc(1).->("Eins"). Хорошо. Теперь вернемся к исходному вопросу с символом подчеркивания:

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

Здесь подчеркивание сокращает следующий эквивалентный код:

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

Обратите внимание, что метод map объекта Map передает кортеж key и value в аргумент функции. Поскольку нас интересуют только значения (строки), мы извлекаем их с помощью метода _2 в кортеже.

14 голосов
/ 22 сентября 2012

В дополнение к блестящим ответам Даниэля и 0__, я должен сказать, что Scala понимает Unicode аналоги для некоторых символов, поэтому вместо

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

можно написать

for (n ← 1 to 10) n % 2 match {
  case 0 ⇒ println("even")
  case 1 ⇒ println("odd")
}
9 голосов
/ 25 октября 2011

<= так же, как вы бы «прочитали» его: «меньше или равно».Так что это математический оператор, в списке < (меньше чем?), > (больше чем?), == (равно?), != (не равно?), <= (меньше или равно?) и >= (больше или равно?).

Это не должно быть перепутано с =>, что является своего рода двойная стрелка вправо , используется для отделения списка аргументов от тела функции и для отделения условия тестирования при сопоставлении с образцом (блок case) от тела, выполняемого при совпадении.Вы можете увидеть пример этого в моих предыдущих двух ответах.Во-первых, функция использует:

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

, которая уже сокращена, так как типы опущены.Следующей функцией будет

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

и использование сопоставления с образцом:

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"
}
9 голосов
/ 25 октября 2011

Относительно :: есть еще одна Stackoverflow запись, которая охватывает случай ::. Короче говоря, он используется для построения Lists с помощью ' consing ' элемента head и tail-списка. Это и класс , который представляет объединенный список и может использоваться в качестве экстрактора, но чаще всего это метод в списке. Как указывает Пабло Фернандес, поскольку он заканчивается двоеточием, он ассоциативно справа , что означает, что получатель вызова метода находится справа, а аргумент слева от оператора. Таким образом, вы можете элегантно выразить выражение как , добавив новый элемент заголовка к существующему списку:

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

Это эквивалентно

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

В качестве объекта-экстрактора используется следующее:

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 (33)"
extract(List(2, 3))   // yields "more than one element"

Здесь это выглядит как оператор, но на самом деле это просто еще один (более читаемый) способ записи

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

Подробнее об экстракторах вы можете прочитать в этом посте .

5 голосов
/ 27 июня 2013

Я считаю, что современная IDE имеет решающее значение для понимания больших проектов Scala. Так как эти операторы также являются методами, по идее, я просто нажимаю клавишу control или control-b в определениях.

Вы можете щелкнуть правой кнопкой мыши в операторе cons (: :) и оказаться в Scala Javadoc, говоря: «Добавляет элемент в начале этого списка». В пользовательских операторах это становится еще более критичным, поскольку они могут быть определены в труднодоступных имплицитах ... ваша IDE знает, где было определено неявное.

4 голосов
/ 26 октября 2016

Просто добавив к другим отличным ответам. Scala предлагает два часто критикуемых символических оператора: /: (foldLeft) и :\ (foldRight), первый из которых является правоассоциативным. Таким образом, следующие три утверждения эквивалентны:

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

Как и эти три:

( 1 to 100 ).foldRight( 0, _+_ )
( 1 to 100 ).:\( 0 )( _+_ )
( ( 1 to 100 ) :\ 0 )( _+_ )
2 голосов
/ 25 октября 2011

Scala наследует большинство арифметических операторов Java . Это включает в себя побитовое или | (одиночный символ канала), побитовое и &, побитовое исключение или ^, а также логическое (логическое) или || (два символа трубы) и логическое и &&. Интересно, что вы можете использовать односимвольные операторы на boolean, поэтому логические операторы java'ish полностью избыточны:

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

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

Как указано в другом посте, вызовы, заканчивающиеся знаком равенства =, разрешаются (если метод с таким именем не существует!) Путем переназначения:

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

Эта «двойная проверка» позволяет легко заменять изменяемую на неизменяемую коллекцию:

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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...