繁体   English   中英

Scala 中大括号和圆括号之间的正式区别是什么,何时应该使用它们?

[英]What is the formal difference in Scala between braces and parentheses, and when should they be used?

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

我从《 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 }

=> 很好。

我曾经尝试过写这个,但最后我放弃了,因为规则有点分散。 基本上,你必须掌握它的窍门。

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

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

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

这里有几个不同的规则和推论:首先,当参数是函数时,Scala 会推断花括号,例如在list.map(_ * 2)中推断花括号,它只是一个较短的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 }都会。

社区努力标准化大括号和圆括号的使用,请参阅 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 中的花括号有什么特别或复杂的地方。 要掌握它们在 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

我认为值得解释它们在函数调用中的用法以及为什么会发生各种事情。 正如有人已经说过的,花括号定义了一个代码块,它也是一个表达式,因此可以放在需要表达式的地方并对其进行评估。 当求值时,它的语句被执行,last 的语句值是整个块求值的结果(有点像在 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。

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

使用括号增加编译检查

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 {
  _ + _
}

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM