在Scala中,大括号和圆括号在形式上有什么区别,什么时候应该使用它们?

将参数传递给括号()和花括号{}中的函数之间的形式区别是什么?

我从 Scala编程书中得到的感觉是,Scala非常灵活,我应该使用我最喜欢的,但我发现有些情况下可以编译,而其他情况则不能。

例如(只是作为一个例子;我很感激任何讨论一般情况的回复,而不仅仅是这个特定的例子):

val tupleList = List[(String, String)]()
val filtered = tupleList.takeWhile( case (s1, s2) => s1 == s2 )

=>错误:简单表达式的非法开始

val filtered = tupleList.takeWhile{ case (s1, s2) => s1 == s2 }

= >好。

59249 次浏览

因为使用的是case,所以定义的是偏函数,而偏函数需要花括号。

社区正在努力标准化大括号和圆括号的使用,参见Scala风格指南(第21页):http://www.codecommit.com/scala-style-guide.pdf

对于高阶方法调用,推荐的语法是总是使用大括号,并跳过点:

val filtered = tupleList takeWhile { case (s1, s2) => s1 == s2 }

对于“普通”方法调用,应该使用点和圆括号。

val result = myInstance.foo(5, "Hello")

这里有一些不同的规则和推论:首先,当参数是函数时,Scala推断花括号,例如在list.map(_ * 2)中,花括号被推断出来,它只是list.map({_ * 2})的缩写形式。其次,Scala允许您跳过最后一个参数列表上的圆括号,如果该参数列表有一个参数并且它是一个函数,那么list.foldLeft(0)(_ + _)可以写成list.foldLeft(0) { _ + _ }(如果您想更显式的话,可以写成list.foldLeft(0)({_ + _}))。

然而,如果你添加case,你会得到一个部分函数,而不是一个函数,而Scala不会推断部分函数的花括号,所以list.map(case x => x * 2)不能工作,但list.map({case x => 2 * 2})list.map { case x => x * 2 }都可以。

我曾经试着写过这方面的文章,但最后还是放弃了,因为规则有点分散。基本上,你得掌握窍门。

也许最好集中在大括号和圆括号可以互换使用的地方:向方法调用传递参数时。你五月用圆括号替换花括号当且仅当方法需要单个参数时。例如:

List(1, 2, 3).reduceLeft{_ + _} // valid, single Function2[Int,Int] parameter


List{1, 2, 3}.reduceLeft(_ + _) // invalid, A* vararg parameter

然而,要更好地掌握这些规则,你还需要了解更多。

增加了使用paren的编译检查

Spray的作者推荐圆形paren,因为它们增加了编译检查。这对于像Spray这样的dsl尤其重要。通过使用paren,你告诉编译器它应该只给出一行;因此,如果你不小心给了它两个或更多,它就会报错。大括号的情况就不同了——例如,如果你在某个地方忘记了一个操作符,那么你的代码就会被编译,你会得到意想不到的结果,而且可能会有一个很难找到的错误。下面是人为的(因为表达式是纯的,至少会给出一个警告),但说明了这一点:

method {
1 +
2
3
}


method(
1 +
2
3
)

第一个编译,第二个给出error: ')' expected but integer literal found。作者想写1 + 2 + 3

有人可能会说,这与带默认参数的多参数方法类似;在使用paren时,不可能不小心忘记用逗号分隔参数。

冗长

关于冗长,一个经常被忽视的重要注意事项。使用花括号不可避免地会导致冗长的代码,因为Scala风格指南清楚地指出右花括号必须在它们自己的行上:

…闭大括号紧跟在最后一个大括号后面的另一行上 函数的直线。

许多自动重新格式化程序(如IntelliJ)将自动为您执行这种重新格式化。所以尽量坚持使用圆括号。

中缀表示法

当使用中缀表示法时,如List(1,2,3) indexOf (2),如果只有一个参数,可以省略圆括号,并将其写成List(1, 2, 3) indexOf 2。这不是点符号的情况。

还要注意,当你有一个多令牌表达式的参数时,比如x + 2a => a % 2 == 0,你必须使用圆括号来表示表达式的边界。

元组

因为有时可以省略圆括号,有时元组需要额外的圆括号,如((1, 2)),有时可以省略外圆括号,如(1, 2)。这可能会引起混乱。

使用case的函数/部分函数字面量

Scala有函数和部分函数字面量的语法。它是这样的:

{
case pattern if guard => statements
case pattern => statements
}

你可以使用case语句的唯一其他地方是matchcatch关键字:

object match {
case pattern if guard => statements
case pattern => statements
}
try {
block
} catch {
case pattern if guard => statements
case pattern => statements
} finally {
block
}

不能在任何其他上下文中使用case语句。所以,如果你想使用case,你需要需要花括号。如果您想知道函数和部分函数之间的区别是什么,答案是:上下文。如果Scala需要一个函数,你就会得到一个函数。如果它期望一个偏函数,你就得到一个偏函数。如果两者都是预期的,则给出关于歧义的错误。

表达式和块

括号可以用来生成子表达式。花括号可以用来构成代码块(这是一个函数字面量,所以要小心试图像使用它一样使用它)。代码块由多条语句组成,每个语句可以是import语句、声明或表达式。它是这样的:

{
import stuff._
statement ; // ; optional at the end of the line
statement ; statement // not optional here
var x = 0 // declaration
while (x < 10) { x += 1 } // stuff
(x % 5) + 1 // expression
}


( expression )

所以,如果你需要声明,多条语句,import或类似的东西,你需要花括号。由于表达式是语句,括号可以出现在花括号内。但有趣的是,代码块是表达式,所以你可以在任何内部表达式中使用它们:

( { var x = 0; while (x < 10) { x += 1}; x } % 5) + 1

因此,由于表达式是语句,代码块是表达式,下面的所有内容都是有效的:

1       // literal
(1)     // expression
{1}     // block of code
({1})   // expression with a block of code
{(1)}   // block of code with an expression
({(1)}) // you get the drift...

它们不能互换的地方

基本上,你不能用()替换{},反之亦然。例如:

while (x < 10) { x += 1 }

这不是一个方法调用,所以不能以其他方式编写它。好吧,你可以用花括号内部作为condition的括号,也可以用括号内部作为代码块的花括号:

while ({x < 10}) { (x += 1) }

所以,我希望这能有所帮助。

我认为有必要解释一下它们在函数调用中的用法以及为什么会发生各种事情。有人已经说过花括号定义了一个代码块,它也是一个表达式,所以可以放在表达式需要的地方,它将被求值。当被求值时,它的语句被执行,最后的语句值是整个块求值的结果(有点像Ruby)。

有了它,我们可以做以下事情:

2 + { 3 }             // res: Int = 5
val x = { 4 }         // res: x: Int = 4
List({1},{2},{3})     // res: List[Int] = List(1,2,3)

最后一个例子只是一个带有三个参数的函数调用,其中每个参数都首先被求值。

现在来看看它是如何处理函数调用的,让我们定义一个简单的函数,将另一个函数作为参数。

def foo(f: Int => Unit) = { println("Entering foo"); f(4) }

要调用它,我们需要传递一个函数,该函数接受一个Int类型的参数,因此我们可以使用function literal并将它传递给foo:

foo( x => println(x) )

现在就像之前说的,我们可以用代码块来代替表达式,让我们使用它

foo({ x => println(x) })

这里发生的事情是,{}内的代码被求值,函数值作为块求值的值返回,然后将这个值传递给foo。这在语义上与前面的调用相同。

但我们还可以添加更多内容:

foo({ println("Hey"); x => println(x) })

现在我们的代码块包含两个语句,因为它是在foo执行之前求值的,所以首先打印“Hey”,然后将函数传递给foo,打印“进入foo”,最后打印“4”。

这看起来有点丑,Scala允许我们在这种情况下跳过括号,所以我们可以这样写:

foo { println("Hey"); x => println(x) }

foo { x => println(x) }

这看起来好多了,和前面的一样。这里仍然先计算代码块,并将计算结果(x => println(x))作为参数传递给foo。

我不认为Scala中的花括号有什么特别或复杂的地方。要掌握它们在Scala中看似复杂的用法,只需记住几件简单的事情:

  1. 花括号形成一个代码块,计算到最后一行代码(几乎所有语言都这样做)
  2. 如果需要,可以使用代码块生成函数(遵循规则1)
  3. 除了case子句外,单行代码可以省略花括号(Scala选择)
  4. 括号可以在函数调用中省略,将代码块作为参数(Scala选择)

让我们根据以上三条规则来解释几个例子:

val tupleList = List[(String, String)]()
// doesn't compile, violates case clause requirement
val filtered = tupleList.takeWhile( case (s1, s2) => s1 == s2 )
// block of code as a partial function and parentheses omission,
// i.e. tupleList.takeWhile({ case (s1, s2) => s1 == s2 })
val filtered = tupleList.takeWhile{ case (s1, s2) => s1 == s2 }


// curly braces omission, i.e. List(1, 2, 3).reduceLeft({_+_})
List(1, 2, 3).reduceLeft(_+_)
// parentheses omission, i.e. List(1, 2, 3).reduceLeft({_+_})
List(1, 2, 3).reduceLeft{_+_}
// not both though it compiles, because meaning totally changes due to precedence
List(1, 2, 3).reduceLeft _+_ // res1: String => String = <function1>


// curly braces omission, i.e. List(1, 2, 3).foldLeft(0)({_ + _})
List(1, 2, 3).foldLeft(0)(_ + _)
// parentheses omission, i.e. List(1, 2, 3).foldLeft(0)({_ + _})
List(1, 2, 3).foldLeft(0){_ + _}
// block of code and parentheses omission
List(1, 2, 3).foldLeft {0} {_ + _}
// not both though it compiles, because meaning totally changes due to precedence
List(1, 2, 3).foldLeft(0) _ + _
// error: ';' expected but integer literal found.
List(1, 2, 3).foldLeft 0 (_ + _)


def foo(f: Int => Unit) = { println("Entering foo"); f(4) }
// block of code that just evaluates to a value of a function, and parentheses omission
// i.e. foo({ println("Hey"); x => println(x) })
foo { println("Hey"); x => println(x) }


// parentheses omission, i.e. f({x})
def f(x: Int): Int = f {x}
// error: missing arguments for method f
def f(x: Int): Int = f x

增加了使用paren的编译检查

Spray的作者建议使用round paren增加编译检查。这对于像Spray这样的dsl尤其重要。通过使用paren,你是在告诉编译器,它应该只给出一行,因此如果你不小心给了它两行或更多,它会报错。花括号就不是这样了,例如,如果你在代码将要编译的地方忘记了一个运算符,你就会得到意想不到的结果,并且可能会发现一个很难找到的错误。下面是虚构的(因为表达式是纯的,至少会给出一个警告),但说明了这一点

method {
1 +
2
3
}


method(
1 +
2
3
)

第一个编译,第二个给出作者想要编写的error: ')' expected but integer literal found.

有人可能会说,这与带默认参数的多参数方法类似;在使用paren时,不可能不小心忘记用逗号分隔参数。

冗长

关于冗长,一个经常被忽视的重要注意事项。使用花括号不可避免地会导致冗长的代码,因为scala样式指南清楚地指出,右花括号必须在它们自己的行上:http://docs.scala-lang.org/style/declarations.html "…闭大括号在函数的最后一行之后的另一行上。”许多自动重新格式化程序(如Intellij)将自动为您执行这种重新格式化。所以尽量坚持使用圆括号。例如:List(1, 2, 3).reduceLeft{_ + _}变成:

List(1, 2, 3).reduceLeft {
_ + _
}

用大括号,你会得到分号,而括号没有。考虑takeWhile函数,因为它需要部分函数,所以只有{case xxx => ??? }是有效的定义,而不是大小写表达式周围的括号。

理想编码风格中的圆括号基本上用于单行代码。 但如果特定的代码段是多行,则使用大括号是更好的方法