Что означают все символические операторы Scala?

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

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

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

Ответ 1

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

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

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

->    // Automatically imported method
||=   // Syntactic sugar
++=   // Syntactic sugar/composition or common method
<=    // Common method
_._   // Typo, though it 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 Спецификация (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. Однако есть одно соглашение, которое вы должны знать при поиске методов. Методы, заканчивающиеся на двоеточие (:), связываются справа, а не слева. Другими словами, хотя вышеупомянутый вызов метода эквивалентен:

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.

Ответ 2

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

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

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

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

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

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

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

Ответ 3

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

_._ содержит период. Период в Scala всегда указывает вызов метода. Итак, слева от периода у вас есть получатель, а справа от него - сообщение (имя метода). Теперь _ является специальным символом в Scala. Есть несколько сообщений об этом, например в этой записи в блоге все варианты использования. Здесь это короткая вырезка анонимной функции, то есть это ярлык для функции, которая принимает один аргумент и вызывает метод _ на нем. Теперь _ не является допустимым методом, поэтому, наверняка, вы видели _._1 или что-то подобное, то есть вызывать метод _._1 в аргументе функции. _1 to _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 карты передает в кортеж ключа и значение аргумент функции. Поскольку нас интересуют только значения (строки), мы извлекаем их с помощью метода _2 на кортеже.

Ответ 4

В дополнение к блестящим ответам Daniel и 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")
}

Ответ 5

Относительно :: есть еще одна запись fooobar.com/questions/3523/..., которая охватывает случай ::. Короче говоря, он используется для построения Lists с помощью consing "элемента head и хвостового списка. Это как класс, который представляет собой список cons'ed и который может использоваться как экстрактор, но чаще всего это метод на список. Как указывает Пабло Фернандес, поскольку он заканчивается в двоеточии, это правильный ассоциативный, то есть получатель вызова метода находится справа, а аргумент слева от оператора. Таким образом, вы можете элегантно выражать consing как добавление нового элемента head в существующий список:

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"
}

Подробнее об экстракторах читайте в этот пост.

Ответ 6

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

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

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"
}

Ответ 7

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

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

Ответ 8

Просто добавив к другим отличные ответы. 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 )( _+_ )

Ответ 9

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`

Эта "двойная проверка" позволяет легко обменивать mutable для неизменяемой коллекции:

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)