Что означают все символические операторы 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)