Scala和括号之间Scala的形式差异是什么,何时应该使用它们?

时间:2010-12-08 09:53:03

标签: scala syntax parentheses braces

将参数传递给括号()和括号{}中的函数有什么形式上的区别?

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

=>细

9 个答案:

答案 0 :(得分:344)

我试过一次写这个,但最后我放弃了,因为规则有些分散。基本上,你必须掌握它。

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

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

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

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

使用parens增加编​​译检查

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

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
)

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

有人可能认为它与具有默认参数的多参数方法类似;使用parens时,不可能不小心忘记用逗号分隔参数。

冗长

关于冗长的一个经常被忽视的重要说明。使用花括号不可避免地导致冗长的代码,因为Scala style guide明确指出关闭花括号必须在它们自己的行上:

  

......关闭括号紧跟在最后一个之后   功能线。

许多自动重组器(如IntelliJ)会自动为您重新格式化。因此,尽可能坚持使用圆形的parens。

中缀表示法

使用中缀表示法时,如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 }

这不是方法调用,因此您无法以任何其他方式编写它。好吧,你可以把花括号放在<{1}}的括号内,并在代码块的花括号里面使用括号

condition

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

答案 1 :(得分:55)

这里有几个不同的规则和推论:首先,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 }都会。

答案 2 :(得分:23)

社区努力标准化括号和圆括号的使用,请参阅Scala样式指南(第21页):http://www.codecommit.com/scala-style-guide.pdf

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

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

对于“普通”的metod调用,你应该使用圆点和圆括号。

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

答案 3 :(得分:15)

我不认为Scala中的花括号有任何特殊或复杂的内容。要掌握Scala中看似复杂的用法,只需记住几个简单的事情:

  1. 花括号形成一个代码块,它代表最后一行代码(几乎所有语言都这样做)
  2. 如果需要,可以使用代码块生成函数(遵循规则1)
  3. 除了case子句(Scala choice)
  4. 之外,单行代码可以省略花括号
  5. 括号可以在函数调用中省略,代码块作为参数(Scala选项)
  6. 让我们按照上述三条规则解释几个例子:

    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
    

答案 4 :(得分: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,“输入foo”被打印,最后“4 “打印出来。

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

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

foo { x => println(x) }

看起来更好,相当于前者。这里仍然首先评估代码块,并将评估结果(x =&gt; println(x))作为参数传递给foo。

答案 5 :(得分:6)

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

答案 6 :(得分:4)

使用parens增加编​​译检查

Spray的作者,建议圆形的parens增加编​​译检查。这对于像Spray这样的DSL尤为重要。通过使用parens你告诉编译器它应该只给一行,因此如果你不小心给它两个或更多,它会抱怨。现在这不是花括号的情况,例如,如果你忘记了某个代码将编译的操作符,你会得到意想不到的结果,并且可能是一个非常难以找到的bug。下面是设计的(因为表达式是纯粹的并且至少会给出警告),但是重点是

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&#34; ...右括号紧跟在最后一行功能线。&#34;许多自动重新格式化程序(如Intellij)将自动为您执行此重新格式化。因此,尽可能坚持使用圆形的parens。例如。 List(1, 2, 3).reduceLeft{_ + _}成为:

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

答案 7 :(得分:0)

理想的编码样式中的假体基本上用于单行代码。 但是,如果特定代码段是多行的,那么使用花括号是一种更好的方法。

答案 8 :(得分:-3)

使用大括号,你会得到分号,而不是括号。考虑takeWhile函数,因为它期望部分函数,​​只有{case xxx => ??? }是有效的定义而不是围绕case表达式的括号。