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

0__*_*0__ 393 scala operators

Scala语法有很多符号.由于使用搜索引擎很难找到这些类型的名称,因此全面列出这些名称会很有帮助.

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

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

Dan*_*ral 515

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

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

幸运的是,大多数类别都在问题中表示:

->    // 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
Run Code Online (Sandbox Code Playgroud)

大多数这些方法的确切含义取决于定义它们的类.例如,<=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
Run Code Online (Sandbox Code Playgroud)

这些都是语言的一部分,因此,可以在任何正确描述语言的文本中找到,例如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
Run Code Online (Sandbox Code Playgroud)

不过,我可能忘记了其他一些意思.

自动导入的方法

因此,如果您没有在上面的列表中找到您要查找的符号,那么它必须是一种方法,或者是一种方法的一部分.但是,通常,您会看到一些符号,而该类的文档将没有该方法.发生这种情况时,要么是使用其他方法查看一个或多个方法的组合,要么已将方法导入范围,或者通过导入的隐式转换可用.

这些仍然可以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._
Run Code Online (Sandbox Code Playgroud)

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

Predef快速查看内部显示一些符号:

class <:<
class =:=
object <%<
object =:=
Run Code Online (Sandbox Code Playgroud)

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

"a" -> 1  // Look for an implicit from String, AnyRef, Any or type parameter
Run Code Online (Sandbox Code Playgroud)

在上面的例子中,通过采用类型对象的方法->在类中定义,其中是同一方法的无界类型参数.ArrowAssocany2ArrowAssocAA

常用方法

因此,许多符号只是一个类的方法.例如,如果你这样做

List(1, 2) ++ List(3, 4)
Run Code Online (Sandbox Code Playgroud)

您将++在ScalaDoc for List上找到该方法.但是,在搜索方法时必须注意一个约定.以colon(:)结尾的方法绑定到右侧而不是左侧.换句话说,虽然上面的方法调用相当于:

List(1, 2).++(List(3, 4))
Run Code Online (Sandbox Code Playgroud)

相反1 :: List(2, 3),如果我这相当于:

List(2, 3).::(1)
Run Code Online (Sandbox Code Playgroud)

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

1 +: List(2, 3) :+ 4
Run Code Online (Sandbox Code Playgroud)

第一个方法(+:)绑定到右边,并在上面找到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
Run Code Online (Sandbox Code Playgroud)

最后一个很有趣,因为任何符号方法都可以组合起来形成类似赋值的方法.

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

(_+_) // 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.
Run Code Online (Sandbox Code Playgroud)

  • @MikeStay不,我的意思是'val ex(c)= 2`. (3认同)

Pab*_*dez 23

Scala和其他语言之间的一个(良好的,IMO)区别在于它允许您使用几乎任何字符命名您的方法.

你所列举的不是"标点符号",而是简单明了的方法,因此它们的行为因对象而异(尽管有一些约定).

例如,检查ListScaladoc文档,您将看到此处提到的一些方法.

要注意的一些事项:

  • 大多数情况下,A operator+equal B组合转化为A = A operator B,例如||=或者++=示例.

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

您可以通过浏览Scala文档找到大多数答案.在这里保留一个参考将重复努力,它会很快落后:)


0__*_*0__ 20

您可以根据某些标准对这些进行分组.在这篇文章中,我将解释下划线字符和右箭头.

_._包含一段时期.Scala中的句点始终表示方法调用.所以在你有接收器的那段时间左边,右边是消息(方法名称).现在_是Scala中的一个特殊符号.有几篇关于它的帖子,例如这个博客条目的所有用例.这是一个匿名函数快捷方式,它是一个函数的快捷方式,它接受一个参数并_在其上调用方法.现在_不是一个有效的方法,所以你肯定会看到_._1或类似的东西,即_._1在函数参数上调用方法._1to _22是提取元组的特定元素的元组的方法.例:

val tup = ("Hallo", 33)
tup._1 // extracts "Hallo"
tup._2 // extracts 33
Run Code Online (Sandbox Code Playgroud)

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

val coll = Map(1 -> "Eins", 2 -> "Zwei", 3 -> "Drei")
Run Code Online (Sandbox Code Playgroud)

Wooop,已经出现了另一个奇怪的标点符号.连字符和大于字符,类似于右箭头,是一个产生一个Tuple2.所以写作的结果没有区别,(1, "Eins")或者1 -> "Eins"只是后者更容易阅读,特别是在像地图例子这样的元组列表中.这->不是魔术,它可以像其他一些运算符一样可用,因为你在范围内的对象中有所有隐式转换scala.Predef.这里发生的转换是

implicit def any2ArrowAssoc [A] (x: A): ArrowAssoc[A] 
Run Code Online (Sandbox Code Playgroud)

哪里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)
Run Code Online (Sandbox Code Playgroud)

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

coll.map(tup => tup._2.reverse)
Run Code Online (Sandbox Code Playgroud)

请注意,mapMap 的方法将key和value的元组传递给function参数.由于我们只对值(字符串)感兴趣,因此我们使用_2元组上的方法提取它们.


om-*_*nom 15

作为Daniel和0__的精彩答案的补充,我不得不说Scala了解某些符号的Unicode模拟,所以不是

for (n <- 1 to 10) n % 2 match {
  case 0 => println("even")
  case 1 => println("odd")
}
Run Code Online (Sandbox Code Playgroud)

一个人可以写

for (n ? 1 to 10) n % 2 match {
  case 0 ? println("even")
  case 1 ? println("odd")
}
Run Code Online (Sandbox Code Playgroud)


0__*_*0__ 10

关于::还有另一个覆盖案例的Stackoverflow条目::.总之,它是用于构建Lists由" consing "一个磁头元件和一个尾列表.它既是一个代表一个cons'ed列表的,也可以用作提取器,但最常见的是它是一个列表方法.正如Pablo Fernandez指出的那样,因为它以冒号结束,所以它是右关联的,意味着方法调用的接收者是右边的,而操作符左边的参数.通过这种方式,您可以优雅地表达内容,将新的head元素添加到现有列表中:

val x = 2 :: 3 :: Nil  // same result as List(2, 3)
val y = 1 :: x         // yields List(1, 2, 3)
Run Code Online (Sandbox Code Playgroud)

这相当于

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

用作提取器对象如下:

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"
Run Code Online (Sandbox Code Playgroud)

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

def extract2(l: List[Int]) = l match {
   case Nil            => "empty"
   case ::(head, Nil)  => "exactly one element (" + head + ")"
   case ::(head, tail) => "more than one element"
}
Run Code Online (Sandbox Code Playgroud)

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


0__*_*0__ 9

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

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

coll.map(tup => tup._2.reverse)
Run Code Online (Sandbox Code Playgroud)

已经缩写为省略的类型.以下功能将是

// function arguments         function body
(tup: Tuple2[Int, String]) => tup._2.reverse
Run Code Online (Sandbox Code Playgroud)

和模式匹配使用:

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"
}
Run Code Online (Sandbox Code Playgroud)

  • 避免这种混淆是为什么我决定开始使用右双箭头(\ U21D2)的unicode字符,右边的"map"箭头(\ U2192)和左边的单个"in"箭头(\ U2190).Scala支持这一点但我有点怀疑,直到我尝试了一段时间.只需查看如何将这些代码点绑定到系统上方便的组合键.OS X非常简单. (4认同)

nai*_*rbv 5

我认为现代IDE对于理解大型scala项目至关重要.由于这些运算符也是方法,因此我只需按控制点击或控制b即可进入定义.

你可以控制点击右边的一个cons运算符(::)并最终在scala javadoc说"在这个列表的开头添加一个元素".在用户定义的运算符中,这变得更加重要,因为它们可以用难以发现的含义定义......您的IDE知道隐式定义的位置.


Mr *_* MT 5

只是添加到其他优秀的答案。Scala 提供了两个经常受到批评的符号运算符,/:( foldLeft) 和:\( foldRight) 运算符,第一个是右结合运算符。所以下面三个语句是等价的:

( 1 to 100 ).foldLeft( 0, _+_ )
( 1 to 100 )./:( 0 )( _+_ )
( 0 /: ( 1 to 100 ) )( _+_ )
Run Code Online (Sandbox Code Playgroud)

就像这三个:

( 1 to 100 ).foldRight( 0, _+_ )
( 1 to 100 ).:\( 0 )( _+_ )
( ( 1 to 100 ) :\ 0 )( _+_ )
Run Code Online (Sandbox Code Playgroud)