花括号和括号在Scala中的形式差异是什么,何时使用?


329

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

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

=>很好。

Answers:


365

我曾经尝试写过一次,但是由于规则有些分散,最终我还是放弃了。基本上,您必须掌握它。

也许最好集中在大括号和括号可以互换使用的地方:将参数传递给方法调用时。你可以用大括号如果更换括号,当且仅当该方法需要一个参数。例如:

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

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

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

使用parens增加了编译检查

Spray的作者建议使用圆括号,因为它们可以提高编译检查的效率。这对于Spray等DSL来说尤其重要。通过使用parens,您是在告诉编译器应该只给它一行。因此,如果您不小心给它两个或更多,它会抱怨。现在,花括号不是这种情况–例如,如果您忘记了某个位置的运算符,则您的代码将被编译,并且您会得到意想不到的结果,并且可能会发现很难发现的错误。下面是人为的(因为表达式是纯净的,至少会给出警告),但指出了这一点:

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
)

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

有人可能会说,对于带有默认参数的多参数方法来说,这是相似的。使用parens时,会意外地忘记逗号分隔参数的可能性。

细度

关于冗长性的一个经常被忽略的重要提示。使用大括号不可避免地会导致冗长的代码,因为Scala样式指南明确指出,右大括号必须位于单独的行上:

…右括号紧接在函数的最后一行之后。

许多自动重新格式化程序(如IntelliJ)将自动为您重新格式化。因此,请尽可能尝试使用圆括号。

中缀符号

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

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

元组

由于有时可以省略括号,因此有时元组需要额外的括号,如in ((1, 2)),而有时可以省略外部括号,如in (1, 2)。这可能会引起混乱。

函数/部分函数文字,带有 case

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

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

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

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

因此,我希望这会有所帮助。


53
这就是为什么人们认为Scala很复杂。我自称是Scala爱好者。
andyczerwonka

我认为不必为每种方法都引入范围,这会使Scala代码更简单!理想情况下,不应使用任何方法{}-一切都应该是一个纯表达式
samthebest

1
@andyczerwonka我完全同意,但这是您为灵活性和表现力所付出的自然而不可避免的价格(?)=> Scala并不过高。对于任何特定情况,这是否是正确的选择当然是另一回事。
Ashkan Kh。Nazary

您好,当您说List{1, 2, 3}.reduceLeft(_ + _)无效时,您是说它的语法错误吗?但是我发现代码可以编译。我把我的代码在这里
卡尔文

List(1, 2, 3)在所有示例中都使用,而不是List{1, 2, 3}。Sc,在Scala的当前版本(2.13)上,此操作失败,并显示不同的错误消息(意外的逗号)。您可能必须回到2.7或2.8才能得到原始错误。
Daniel C. Sobral

56

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


4
不仅是最后一个参数列表。例如,list.foldLeft{0}{_+_}作品。
Daniel C. Sobral

1
啊,我确定我已经读到它只是最后一个参数列表,但显然我错了!很高兴知道。
Theo 2010年

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

18
实际上,惯例是使用大括号,该链接是非官方的。这是因为在函数式编程中,所有函数都是一阶公民,因此不应区别对待。其次,马丁·奥德斯基(Martin Odersky)说,您应该只对操作符之类的方法使用infix(例如+--),不正规的方法,如takeWhile。缀标记的全部要点是允许DSL和自定义运营商,因此不应在此上下文中始终使用它。
samthebest 2014年

17

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

  1. 大括号形成一个代码块,该代码块求值到代码的最后一行(几乎所有语言都这样做)
  2. 如果需要,可以使用代码块生成函数(遵循规则1)
  3. 对于单行代码,可以省略花括号,但case子句除外(Scala选择)
  4. 以代码块为参数的函数调用中可以省略括号(选择标度)

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

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

1.并非所有语言都正确。4.在Scala中并不是真的。例如:def f(x:Int)= fx
aij

@aij,感谢您的评论。对于1,我暗示了Scala对{}行为的熟悉程度。我更新了措辞,以求精确。而对于4,由于()和之间的交互作用{}(如def f(x: Int): Int = f {x}作品)有些棘手,这就是我获得第五名的原因。:)
lcn

1
我倾向于认为()和{}在Scala中几乎可以互换,只是它对内容的解析不同。我通常不写f({x}),所以f {x}不想像用curly代替括号那样去掉括号。其他语言实际上确实允许您省略parethese,例如,fun f(x) = f x在SML中有效。
aij 2015年

@aij,将其f {x}视为f({x})似乎对我来说是一个更好的解释,因为思考(){}互换性较不直观。顺便说一下,该f({x})解释在某种程度上受Scala规范的支持(第6.6节):ArgumentExprs ::= ‘(’ [Exprs] ‘)’ | ‘(’ [Exprs ‘,’] PostfixExpr ‘:’ ‘_’ ‘*’ ’)’ | [nl] BlockExp
lcn

13

我认为有必要解释它们在函数调用中的用法以及发生各种事情的原因。正如有人已经说过的,花括号定义了一个代码块,它也是一个表达式,因此可以放在期望表达式的地方并对其求值。进行评估时,将执行其语句,并且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。


1
是我吗 但实际上我确实更喜欢foo({ x => println(x) })。也许我太
迷恋

7

因为您正在使用case,所以您正在定义部分函数,​​并且部分函数需要花括号。


1
我通常要求一个答案,而不仅仅是这个示例的答案。
马克·弗朗索瓦

5

使用parens增加了编译检查

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

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
 )

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

有人可能会说,对于带有默认参数的多参数方法,它是相似的。使用parens时,会偶然忘记逗号分隔参数的可能性。

细度

关于冗长性的一个经常被忽略的重要提示。使用花括号不可避免地会导致冗长的代码,因为scala样式指南明确指出,右花括号必须位于自己的行上:http : //docs.scala-lang.org/style/declarations.html “ ...在函数的最后一行之后紧跟着一行。” 像Intellij中一样,许多自动重新格式化程序会自动为您重新格式化。因此,请尽量使用圆形括号。例如List(1, 2, 3).reduceLeft{_ + _}变为:

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

-2

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.