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

402

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

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

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

0__
источник
4
и индекс Лестницы 1-е издание, на >> artima.com/pins1ed/book-index.html#indexanchor
Gene T
2
Связанный: символы оператора против буквенно-цифровых символов: stackoverflow.com/questions/7656937/…
Луиджи Плиндж
1
Кроме того, если есть «операторы» (в основном это методы, с несколькими именами классов, использующими инфикс), которые вы не можете найти в scalex или в лестничной книге, например, «!!», вероятными источниками являются скалярные документы для akka, scalaz и сб
Джин Т
пример использования имени класса infix (на немецком языке) >> raichoo.blogspot.com/2010/06/spass-mit-scala-infixtypen.html
Gene T
Что касается вопроса фильтрации по поисковым системам, Symbolhound.com также является хорошей альтернативой
Патрик Рефондини

Ответы:

526

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

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

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

->    // 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 (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 : вам просто нужно знать, где их искать. Или, если это не удалось, посмотрите на индекс (в настоящее время не работает на 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 for 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.
Даниэль С. Собрал
источник
1
Вы имели в виду val c = ex(2)вместо val ex(c) = 2?
Майк Остаться
3
@MikeStay Нет, я имел в виду val ex(c) = 2.
Даниэль С. Собрал,
О, он использует синтаксис сопоставления с образцом. Спасибо.
Майк Остаться
=> также присваивает статус 'call by name' при использовании между: и типом как у: => Int '
Стивен У. Райт
1
Может быть, стоит также упомянуть: / и: \ действительно не интуитивные операторы. Таким образом, map.foldLeft (initialVal) аналогичен (initialVal: / map) -: \ вместо этого является foldRight.
Мистер MT
24

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

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

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

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

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

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

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

Пабло Фернандес
источник
21

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

_._содержит точку Точка в 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метода из кортежа.

0__
источник
+1 У меня были проблемы с попыткой понять ->метод, но ваше предложение «Так что нет никакой разницы в результатах написания (1, "Eins")или 1 -> "Eins"», помогло мне понять синтаксис и его использование.
Джесси Уэбб
К сведению, ваша ссылка на запись в блоге мертва
still_learning
15

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

Что касается ::еще одной записи Stackoverflow, которая охватывает ::дело. Короче говоря, он используется для конструирования Listsпутем « объединения » элемента head и списка хвостов. Это и класс, который представляет объединенный список, и его можно использовать в качестве экстрактора, но чаще всего это метод в списке. Как указывает Пабло Фернандес, поскольку он заканчивается двоеточием, он ассоциативно справа , то есть получатель вызова метода находится справа, а аргумент слева от оператора. Таким образом, вы можете элегантно выразить это как добавление нового элемента 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 (1)"
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"
}

Вы можете прочитать больше об экстракторах в этом посте .

0__
источник
9

<=это как «читать»: «меньше или равно». Таким образом, это математический оператор в списке <(меньше чем?), >(Больше чем?), ==(Равно?), !=(Не равно?), <=(Меньше или равно?) И >=(больше чем или равно?)

Это не следует путать с тем, =>что является разновидностью двойной правой стрелки , используемой для отделения списка аргументов от тела функции и для отделения условия тестирования в сопоставлении с образцом ( 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"
}
0__
источник
4
Чтобы избежать этой путаницы, я решил начать использовать символы Юникода для правой двойной стрелки (\ U21D2), единственной правой стрелки "map" (\ U2192) и левой единственной стрелки "in" (\ U2190). Scala поддерживает это, но я немного скептически относился, пока не попробовал некоторое время. Просто посмотрите, как связать эти кодовые точки с удобной комбинацией клавиш в вашей системе. Это было действительно легко на OS X.
Коннор Дойл
5

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

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

nairbv
источник
4

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

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)
0__
источник
4
PS Существует различие между использованием однозначных и двухсимвольных операторов на логических значениях - первый стремится (все термины оцениваются), а последний завершается рано, если получен логический результат: true | { println( "Icke" ); true }⇒ печатает! true || { println( "Icke" ); true }не печатает!
0