Scala的所有符号运算符是什么意思?


402

Scala语法有很多符号。由于使用搜索引擎很难找到这类名称,因此将其完整列出会很有帮助。

Scala中的所有符号都是什么,它们每个都做什么?

我特别想知道->||=++=<=_._::,和:+=


4
以及《楼梯》第1版的索引,位于>> artima.com/pins1ed/book-index.html#indexanchor
Gene T

2
相关:运营商角色VS字母数字字符:stackoverflow.com/questions/7656937/...
路易吉Plinge

1
另外,如果您在scalex或楼梯书中找不到“操作符”(主要是方法,并且使用了一些用中缀表示的类名),例如“ !!”,则可能是akka,scalaz的scaladocs和sbt
基因T

使用中缀的类名的示例(德语)>> raichoo.blogspot.com/2010/06/spass-mit-scala-infixtypen.html
基因T

关于搜索引擎过滤的问题,symbolhound.com也是一个不错的选择
Patrick Refondini 2014年

Answers:


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

这些方法大多数的确切含义取决于定义它们的类。例如,<=on 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
Mike Stay

3
@MikeStay不,我的意思是val ex(c) = 2
Daniel C. Sobral 2014年

哦,它使用模式匹配语法。谢谢。
Mike Stay

=>也之间使用时赋予“呼叫按名称”状态:和式中Y:=> INT”
斯蒂芬W.莱特

1
也许还应该提到:/和:\确实不直观的运算符。因此map.foldLeft(initialVal)与(initialVal:/ map)-:\是foldRight。
MT先生

24

Scala与其他语言之间的一个(好的,IMO)区别是,它使您几乎可以用任何字符来命名方法。

您所列举的不是“标点符号”,而是简单明了的方法,因此,它们的行为在一个对象与另一个对象之间有所不同(尽管有一些约定)。

例如,查看Scaladoc文档以获取列表,您将看到此处提到的一些方法。

注意事项:

  • 在大多数情况下,A operator+equal B组合会转换为A = A operator B,例如||=或的++=示例。

  • 结尾的方法:是正确的关联,这意味着A :: B实际上是B.::(A)

通过浏览Scala文档,您将找到大多数答案。在此处保留参考将重复工作,并且很快就会落后:)


21

您可以根据某些条件先将它们分组。在本文中,我将仅说明下划线字符和右箭头。

_._包含句点。Scala中的句点始终表示方法调用。因此,在句号的左边有接收方,在消息的右边(方法名)。现在_是Scala中的特殊符号。关于它,有几篇文章,例如此博客条目的所有用例。这是一个匿名函数的捷径,它是一个带有一个参数并调用该方法的函数的快捷方式_在其上。现在_不是有效的方法,因此很可能您已经看到_._1或类似的东西,即_._1在函数参数上调用方法。_1to _22是提取元组特定元素的元组方法。例:

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

现在,假设函数应用程序快捷方式的用例。给定一个将整数映射到字符串的映射:

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

糟糕,标点符号已经再次出现。连字符和大于号(类似于右箭头)是产生的运算符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)

请注意,mapMap 的方法将键和值的元组传递给函数参数。因为我们只对值(字符串)感兴趣,所以我们使用_2元组上的方法提取它们。


+1我在尝试理解该->方法时遇到了麻烦,但是您的句子“因此,在编写结果(1, "Eins")或中没有任何区别1 -> "Eins"”帮助我理解了语法及其用法。
杰西·韦伯

仅供参考,您的博客条目链接已失效
still_learning

15

除了丹尼尔(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")
}

10

关于::另一个涵盖该情况的Stackoverflow条目::。简而言之,它Lists通过“ 浓缩 ”头元素和尾列表来构建。它既是代表约束列表的,也可以用作提取器,但最常见的是它是列表方法。正如Pablo Fernandez指出的那样,由于它以冒号结尾,因此它是右关联的,这意味着方法调用的接收者在运算符的右边,而参数在运算符的左边。这样一来,您就可以在将新的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"
}

您可以在这篇文章中阅读有关提取器的更多信息


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
为避免这种混乱,我决定开始对uni字符使用右双箭头(\ U21D2),右单“映射”箭头(\ U2192)和左单“ in”箭头(\ U2190)。Scala支持这一点,但是直到我尝试了一段时间之后,我还是有些怀疑。只需查找如何将这些代码点绑定到系统上的便捷键组合即可。这是很容易在OS X
康纳·道尔

5

我认为现代IDE对于理解大型scala项目至关重要。由于这些运算符也是方法,因此以intellij的想法,我只需要按住Control键单击或按住Control键b键即可进入定义。

您可以在cons运算符(::)上单击鼠标右键,然后在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 }⇒并不能打印!
2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.