Scala的所有符号运算符都意味着什么?

What do all of Scala's symbolic operators mean?

scala语法有很多符号。由于使用搜索引擎很难找到这类名称,因此综合列出这些名称会有所帮助。

scala中的所有符号都是什么,它们各自是做什么的?

我特别想了解一下->||=++=<=_._:::+=


为了教学目的,我将操作员分为四类:

  • 关键字/保留符号
  • 自动导入的方法
  • 常用方法
  • 句法糖/成分

那么,幸运的是,大多数类别都在这个问题中出现了:

1
2
3
4
5
6
7
->    // 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中有一些特殊的符号。其中两个被认为是正确的关键字,而其他的只是"保留"。他们是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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)本身。

最后一个,下划线,应该有一个特别的描述,因为它被广泛使用,有很多不同的含义。这是一个示例:

1
2
3
4
5
6
7
8
9
10
11
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代码都有三个自动导入:

1
2
3
4
// Not necessarily in this order
import _root_.java.lang._      // _root_ denotes an absolute path
import _root_.scala._
import _root_.scala.Predef._

前两个仅使类和singleton对象可用。第三个包含所有隐式转换和导入的方法,因为Predef本身就是一个对象。

Predef的内部看,很快就会显示出一些符号:

1
2
3
4
class <:<
class =:=
object <%<
object =:=

任何其他符号都将通过隐式转换提供。只需看看用implicit标记的方法,它们作为参数接收类型为的对象,并接收该方法。例如:

1
"a" -> 1  // Look for an implicit from String, AnyRef, Any or type parameter

在上述情况下,->在类ArrowAssoc中通过方法any2ArrowAssoc定义,该方法采用A类型的对象,其中A是同一方法的无边界类型参数。

常用方法

所以,许多符号只是一个类上的方法。例如,如果你这样做

1
List(1, 2) ++ List(3, 4)

您可以在scaladoc for list中找到方法++。但是,在搜索方法时必须注意一个约定。方法以结肠(:号)结尾,向右绑定而不是向左绑定。换句话说,上面的方法调用等价于:

1
List(1, 2).++(List(3, 4))

如果我有,而不是1 :: List(2, 3),那相当于:

1
List(2, 3).::(1)

因此,在查找以冒号结尾的方法时,需要查看右侧的类型。例如,考虑:

1
1 +: List(2, 3) :+ 4

第一种方法(+:与右侧结合,在List上发现。第二种方法(:+只是一种普通的方法,在List上,它又绑定到左边。

句法糖/成分

所以,这里有一些可能隐藏方法的句法成分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

最后一个很有趣,因为任何符号方法都可以通过这种方式组合成类似赋值的方法。

当然,代码中可以出现各种组合:

1
2
3
4
(_+_) // 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.


scala和其他语言的一个区别(很好,imo)是它允许您用几乎任何字符命名方法。

您列举的不是"标点符号",而是简单明了的方法,因此它们的行为因对象而异(尽管有一些惯例)。

例如,检查scaladoc文档中的列表,您将看到这里提到的一些方法。

要记住的一些事情:

  • 大多数时候,A operator+equal B组合转化为A = A operator B,就像在||=++=示例中一样。

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

您可以通过浏览scala文档找到大多数答案。在这里保存一个参考资料会重复工作,而且会很快落在后面:)


您可以根据一些标准先对它们进行分组。在本文中,我将只解释下划线字符和右箭头。

_._包含一个句点。scala中的句点总是表示方法调用。所以周期的左边是接收者,右边是消息(方法名)。现在,_是scala中的一个特殊符号。有几个关于它的文章,例如这个博客条目都是用例。这里是一个匿名函数的快捷方式,也就是说,它是一个函数的快捷方式,该函数接受一个参数并在其上调用方法_。现在,_不是一个有效的方法,所以最肯定的是,您看到了_._1或类似的东西,即在函数参数上调用方法_._1_1_22是元组的方法,它们提取一个元组的特定元素。例子:

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

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

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

呜呜,又出现了一个奇怪的标点符号。连字符和大于号字符类似于右手箭头,是一个产生Tuple2的运算符。所以写(1,"Eins")1 ->"Eins"的结果没有区别,只是后者更容易阅读,特别是在像map示例这样的元组列表中。->不是魔法,它和其他一些操作符一样,是可用的,因为对象scala.Predef在作用域中有所有隐式转换。这里发生的转换是

1
implicit def any2ArrowAssoc [A] (x: A): ArrowAssoc[A]

其中,ArrowAssoc采用->方法创建Tuple2。因此,1 ->"Eins"是实际调用Predef.any2ArrowAssoc(1).->("Eins")。好啊。现在回到带下划线字符的原始问题:

1
2
3
// lets create a sequence from the map by returning the
// values in reverse.
coll.map(_._2.reverse) // yields List(sniE, iewZ, ierD)

下面的下划线缩短了以下等效代码:

1
coll.map(tup => tup._2.reverse)

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


除了Daniel和0_uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

1
2
3
4
for (n <- 1 to 10) n % 2 match {
  case 0 => println("even")
  case 1 => println("odd")
}

一个人可以写作

1
2
3
4
for (n ← 1 to 10) n % 2 match {
  case 0 ? println("even")
  case 1 ? println("odd")
}

<=就像你"读"它一样:"小于或等于"。所以它是一个数学运算符,在<的列表中(小于?),>(大于?),==(等于?),!=(不相等?),<=(小于或等于?)和>=(大于或等于?).

这不能与=>混淆,=>是一种双右箭头,用于将参数列表与函数体分离,并将模式匹配(case块)中的测试条件与匹配发生时执行的体分离。你可以在我之前的两个答案中看到这个例子。首先,功能使用:

1
coll.map(tup => tup._2.reverse)

它已经被省略为类型。以下函数是

1
2
// function arguments         function body
(tup: Tuple2[Int, String]) => tup._2.reverse

模式匹配使用:

1
2
3
4
5
6
7
8
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"
}


关于::,还有另一个stackoverflow条目,涵盖::案例。简而言之,它是通过"考虑"一个头部元素和一个尾部列表来构造Lists。它既是一个表示列表的类,也可以用作提取器,但最常见的是它是列表上的一个方法。正如pablo fernandez指出的,由于它以冒号结尾,所以它是右关联的,这意味着方法调用的接收器在右边,参数在运算符的左边。这样,您就可以优雅地将consing表示为将新的head元素添加到现有列表中:

1
2
val x = 2 :: 3 :: Nil  // same result as List(2, 3)
val y = 1 :: x         // yields List(1, 2, 3)

这相当于

1
2
val x = Nil.::(3).::(2) // successively prepend 3 and 2 to an empty list
val y = x.::(1)         // then prepend 1

使用as提取器对象如下:

1
2
3
4
5
6
7
8
9
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"

这看起来像是这里的一个操作符,但它实际上只是另一种(更可读的)书写方式。

1
2
3
4
5
def extract2(l: List[Int]) = l match {
   case Nil            =>"empty"
   case ::(head, Nil)  =>"exactly one element (" + head +")"
   case ::(head, tail) =>"more than one element"
}

您可以在本文中阅读关于提取器的更多信息。


我认为现代的IDE对于理解大型的scala项目是至关重要的。因为这些操作符也是方法,所以在intellij思想中,我只控制click或control-b的定义。

你可以控制点击右键进入一个cons操作符(:)并在scala javadoc结束,说"在这个列表的开头添加一个元素"。在用户定义的操作符中,这变得更为关键,因为它们可以在难以找到的含义中定义…您的IDE知道隐式的定义位置。


只是增加了其他优秀的答案。scala提供了两个经常受到批评的符号运算符:/:(foldLeft)和:\(foldRight),第一个是右联想运算符。因此,以下三条陈述是等效的:

1
2
3
( 1 to 100 ).foldLeft( 0, _+_ )
( 1 to 100 )./:( 0 )( _+_ )
( 0 /: ( 1 to 100 ) )( _+_ )

这三个是:

1
2
3
( 1 to 100 ).foldRight( 0, _+_ )
( 1 to 100 ).:\( 0 )( _+_ )
( ( 1 to 100 ) :\ 0 )( _+_ )

Scala继承了大部分Java的算术运算符。这包括位或|(单管字符)、位和&、位异或^,以及逻辑(布尔)或||(双管字符)和逻辑和&&。有趣的是,您可以在boolean上使用单字符运算符,因此Java的逻辑运算符是完全冗余的:

1
2
3
4
5
true && true   // valid
true & true    // valid as well

3 & 4          // bitwise-and (011 & 100 yields 000)
3 && 4         // not valid

正如在另一篇文章中指出的,以等号=结尾的调用将被解析(如果不存在具有该名称的方法!)通过重新分配:

1
2
var x = 3
x += 1         // `+=` is not a method in `int`, Scala makes it `x = x + 1`

这种"双重检查"使得可以轻松地将可变集合替换为不可变集合:

1
2
3
4
5
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)