357

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

我从Programming in 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 }

=> 很好。

4

9 回答 9

389

我曾经尝试过写这篇文章,但最后我放弃了,因为规则有些分散。基本上,你必须掌握它。

也许最好专注于花括号和圆括号可以互换使用的地方:将参数传递给方法调用时。当且仅当方法需要单个参数时,您可以用花括号替换括号。例如:

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

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

但是,为了更好地掌握这些规则,您还需要了解更多信息。

使用括号增加编译检查

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

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
)

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

有人可能会争辩说,对于具有默认参数的多参数方法,它是相似的。使用括号时,不可能不小心忘记用逗号分隔参数。

冗长

关于冗长的一个重要的经常被忽视的注释。使用花括号不可避免地会导致代码冗长,因为Scala 样式指南明确指出闭合花括号必须在自己的行中:

…右大括号位于函数最后一行之后的单独一行。

许多自动重新格式化程序,如 IntelliJ 中的,会自动为您执行重新格式化。所以尽量坚持使用圆括号。

中缀符号

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

另请注意,当您有一个参数是多标记表达式时,例如x + 2or a => a % 2 == 0,您必须使用括号来指示表达式的边界。

元组

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

函数/部分函数文字case

Scala 具有函数和部分函数字面量的语法。它看起来像这样:

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

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

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 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 )

所以,如果你需要声明、多条语句、animport或类似的东西,你需要花括号。而且因为表达式是一个语句,括号可能出现在花括号内。但有趣的是代码块也是表达式,因此您可以表达式中的任何位置使用它们:

( { 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) }

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

于 2010-12-08T11:55:04.990 回答
60

这里有几个不同的规则和推论:首先,当参数是函数时,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 }

于 2010-12-08T10:33:57.377 回答
23

社区正在努力标准化大括号和圆括号的使用,请参阅 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")
于 2010-12-08T10:10:41.900 回答
20

我不认为 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
于 2014-07-27T20:07:29.333 回答
14

我认为值得解释它们在函数调用中的用法以及为什么会发生各种事情。正如有人已经说过的,花括号定义了一个代码块,它也是一个表达式,因此可以放在期望表达式的位置并对其进行评估。评估时,它的语句被执行,最后一个语句的值是整个块评估的结果(有点像 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 类型参数的函数,因此我们可以使用函数字面量并将其传递给 foo:

foo( x => println(x) )

现在如前所述,我们可以使用代码块代替表达式,所以让我们使用它

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

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

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

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

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

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

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

或者

foo { x => println(x) }

这看起来好多了,相当于以前的那些。这里仍然首先评估代码块,评估结果(即 x => println(x))作为参数传递给 foo。

于 2012-11-02T10:02:57.780 回答
8

因为您正在使用case,所以您正在定义一个偏函数,而偏函数需要花括号。

于 2010-12-08T09:57:25.523 回答
5

使用括号增加编译检查

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

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
 )

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

有人可能会争辩说,对于具有默认参数的多参数方法,它是相似的。使用括号时,不可能不小心忘记用逗号分隔参数。

冗长

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

List(1, 2, 3).reduceLeft {
  _ + _
}
于 2014-12-29T09:49:55.343 回答
0

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

于 2020-08-06T11:10:51.173 回答
-2

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

于 2017-11-25T10:33:09.650 回答