Scala语法有很多符号。由于使用搜索引擎很难找到这些类型的名称,因此全面列出这些名称会很有帮助。
Scala中的所有符号是什么,它们各自做了什么?
特别是,我想知道->
,||=
,++=
,<=
,_._
,::
和:+=
。
为了教学目的,我将操作员分为四类:
幸运的是,大多数类别都在问题中表示:
-> // 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上找到:你只需要知道在哪里寻找它们。或者,如果不这样做,请查看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.
Scala和其他语言之间的一个(良好的,IMO)区别在于它允许您使用几乎任何字符命名您的方法。
你所列举的不是“标点符号”,而是简单明了的方法,因此它们的行为因对象而异(尽管有一些约定)。
例如,检查Scaladoc documentation for List,你会看到你在这里提到的一些方法。
要注意的一些事项:
A operator+equal B
组合转换为A = A operator B
,如||=
或++=
示例。:
结束的方法是正确的联想,这意味着A :: B
实际上是B.::(A)
。您可以通过浏览Scala文档找到大多数答案。在这里保留一个参考将重复努力,它会很快落后:)
您可以根据某些标准对这些进行分组。在这篇文章中,我将解释下划线字符和右箭头。
_._
包含一个时期。 Scala中的句点始终表示方法调用。所以在你有接收器的那段时间左边,右边是消息(方法名称)。现在_
是Scala中的一个特殊符号。有几个关于它的帖子,例如this blog entry所有用例。这是一个匿名函数快捷方式,它是一个函数的快捷方式,它接受一个参数并在其上调用方法_
。现在_
不是一个有效的方法,所以你肯定会看到_._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")
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的map
方法将key和value的元组传递给function参数。由于我们只对值(字符串)感兴趣,我们在元组上使用_2
方法提取它们。
作为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")
}
<=
就像你会“读”它一样:'小于或等于'。所以它是一个数学运算符,在<
列表中(小于?),>
(大于?),==
(等于?),!=
(不等于?),<=
(小于或等于?),和>=
(大于或等于?)。
这不能与=>
相混淆,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"
}
和模式匹配使用:
::
关于Stackoverflow,还有另一个::
条目,涵盖Lists
案件。简而言之,它用于通过'consing'头部元素和尾部列表来构造class。它既是一个val x = 2 :: 3 :: Nil // same result as List(2, 3)
val y = 1 :: x // yields List(1, 2, 3)
,代表一个cons'ed列表,可以用作提取器,但最常见的是它是一个列表上的方法。正如Pablo Fernandez指出的那样,因为它以冒号结束,所以它是右关联的,意味着方法调用的接收者是右边的,而操作符左边的参数。通过这种方式,您可以优雅地表达内容,将新的head元素添加到现有列表中:
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"
}
这看起来像一个操作符,但它实际上只是另一种(更易读)的写作方式
this post
您可以在/:
中阅读有关提取器的更多信息。
我认为现代IDE对于理解大型scala项目至关重要。由于这些运算符也是方法,因此我只需按控制点击或控制b即可进入定义。
你可以控制点击右边的一个cons运算符(::)并最终在scala javadoc说“在这个列表的开头添加一个元素”。在用户定义的运算符中,这变得更加重要,因为它们可以用难以发现的含义定义......您的IDE知道隐式定义的位置。
只是添加其他优秀的答案。 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 )( _+_ )
这三个是:
Java's arithmetic operators
Scala继承了|
的大部分内容。这包括按位或&
(单管字符),按位和^
,按位异或 - ||
,以及逻辑(布尔)或&&
(两个管道字符)和逻辑和boolean
。有趣的是,你可以在true && true // valid
true & true // valid as well
3 & 4 // bitwise-and (011 & 100 yields 000)
3 && 4 // not valid
上使用单字符运算符,因此java'的逻辑运算符是完全冗余的:
=
正如在另一篇文章中指出的那样,以等号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)
这种“复核”使得可以轻松地将mutable交换为不可变集合:
qazxswpoi