Що означають усі символьні оператори Scala?


402

Синтаксис Scala має багато символів. Оскільки такі імена важко знайти за допомогою пошукових систем, вичерпний перелік їх буде корисним.

Які всі символи у Scala, і що робить кожен з них?

Зокрема, я хотів би знати про те ->, ||=, ++=, <=, _._, ::, і :+=.


4
та індекс Сходи 1-го видання за адресою: >> artima.com/pins1ed/book-index.html#indexanchor
Gene T

2
Пов'язані: символи оператора проти алфавітно - цифрових символів: stackoverflow.com/questions/7656937 / ...
Луїджі Плюм

1
також, якщо є "оператори" (які в основному є методами, в яких використовується декілька назв класу, що використовуються в інфіксі), яких ви не можете знайти в scalex або сходовій книзі, наприклад, "!!", ймовірними джерелами є скаладографи для akka, scalaz і sbt
Gene T

Приклад імені класу використовується Інфікси (німецькою мовою ) >> 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 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 : ви просто повинні знати, де їх шукати. Або, якщо цього не зробити, подивіться на індекс (на даний момент розбитий на 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.
Даніель К. Собрал

О, він використовує синтаксис відповідності шаблону. Дякую.
Майк перебування

=> також надає статус "дзвінок за іменем", коли використовується між: та введіть як у: => Int "
Стівен У. Райт

1
Можливо, слід також згадати про: / і: \ дійсно неінтуїтивні оператори. Отже, map.foldLeft (початковий Val) такий же, як (InitiVal: / map) -: \ замість foldRight.
Містер МТ

24

Одна (гарна, IMO) різниця між Scala та іншими мовами полягає в тому, що вона дозволяє називати свої методи практично будь-якими символами.

Те, що ви перераховуєте, - це не «пунктуація», а прості та прості методи, і як така їх поведінка відрізняється від одного об’єкта до іншого (хоча існують деякі умовності).

Наприклад, перевірте документацію Scaladoc на Список , і ви побачите деякі згадані тут методи.

Деякі речі, які слід пам’ятати:

  • Більшість випадків 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")

Вуо, вже є інше явище дивної пунктуації. Дефіси та символи більше, ніж вони нагадують стрілку праворуч , - це оператор, який виробляє a 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 передається в кордоні ключа і значення аргументу функції. Оскільки нас цікавлять лише значення (рядки), ми витягуємо їх _2методом на кортеж.


+1 У мене виникли проблеми, намагаючись зрозуміти ->метод, але ваше речення "Отже, немає різниці в результаті написання (1, "Eins")або 1 -> "Eins"", допомогло мені зрозуміти синтаксис та його використання.
Джессі Вебб

fyi посилання на ваш запис у блозі мертве
still_learning

15

Як додаток до блискучих відповідей Даниїла та 0__, я мушу сказати, що Скала розуміє аналоги 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шляхом " подачі " головного елемента та списку хвоста. Це і клас, який представляє собою звичний список, і який може бути використаний як екстрактор, але найчастіше це метод у списку. Як зазначає Пабло Фернандес, оскільки він закінчується двокрапкою, він є правильним асоціативним , тобто приймач виклику методу знаходиться праворуч, а аргумент - зліва від оператора. Таким чином , ви можете елегантно висловити consing , як випереджаючи новий головний елемент в існуючий список:

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

Детальніше про витяжки ви можете прочитати в цьому пості .


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

4
Уникаючи цієї плутанини, тому я вирішив почати використовувати символи Unicode для правої подвійної стрілки (\ U21D2), єдиної правої стрілки "карти" (\ U2192) та лівої єдиної стрілки "в" (\ U2190). Scala це підтримує, але я був трохи скептично настроєний, поки не спробував це деякий час. Просто подивіться, як прив’язати ці кодові точки до зручної комбінації клавіш у вашій системі. На ОС X. це було дуже просто
Коннор Дойл,

5

Я вважаю, що сучасний IDE є критично важливим для розуміння великих проектів масштабування. Оскільки ці оператори також є методами, в ідеї intellij я просто контролюю клацання або control-b у визначення.

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


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)

4
PS Існує різниця між використанням операторів одинарних та подвійних символів на булевих - перший прагне (всі терміни оцінюються), а останній закінчується достроково, якщо відомий результат булевого типу: true | { println( "Icke" ); true }⇒ друкує! true || { println( "Icke" ); true }не друкує!
0__
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.