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

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

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

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

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

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

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

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

=罚款。


我曾经试过写这篇文章,但最后我放弃了,因为规则有些漫不经心。基本上,你必须掌握它的窍门。好的。

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

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

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

但是,为了更好地掌握这些规则,您还需要了解更多。好的。增加了对parens的编译检查

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

1
2
3
4
5
6
7
8
9
10
11
method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
)

第一个编译,第二个给出error: ')' expected but integer literal found。作者想写一本书。好的。

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

关于冗长的一个重要的经常被忽视的注释。使用大括号不可避免地会导致冗长的代码,因为scala样式指南清楚地指出关闭大括号必须在它们自己的行上:好的。

… the closing brace is on its own line immediately following the last
line of the function.

Ok.

许多自动重新格式化,如intellij中,都会自动为您执行这种重新格式化。所以尽量坚持使用圆帕伦斯。好的。中缀表示法

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

还要注意,当您有一个单参数是多标记表达式时,如x + 2a => a % 2 == 0,您必须使用括号来表示表达式的边界。好的。多元组

因为有时可以省略括号,有时元组需要额外的括号,就像在((1, 2))中一样,有时可以省略外括号,就像在(1, 2)中一样。这可能导致混淆。好的。带case的函数/部分函数文本

scala有函数和部分函数文本的语法。看起来是这样的:好的。

1
2
3
4
{
    case pattern if guard => statements
    case pattern => statements
}

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

1
2
3
4
object match {
    case pattern if guard => statements
    case pattern => statements
}
1
2
3
4
5
6
7
8
try {
    block
} catch {
    case pattern if guard => statements
    case pattern => statements
} finally {
    block
}

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

括号可用于生成子表达式。大括号可以用来制作代码块(这不是一个函数文本,所以要小心尝试像使用一个一样使用它)。代码块由多个语句组成,每个语句都可以是导入语句、声明或表达式。就像这样:好的。

1
2
3
4
5
6
7
8
9
10
{
    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或类似的东西,就需要大括号。由于表达式是一个语句,括号可能出现在大括号内。但有趣的是,代码块也是表达式,因此可以在表达式内的任何位置使用它们:好的。

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

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

1
2
3
4
5
6
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...

不能互换的地方

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

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

这不是方法调用,因此不能以任何其他方式编写。好吧,您可以在condition的圆括号内放置大括号,也可以在代码块的大括号内使用圆括号:好的。

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

所以,我希望这有帮助。好的。好啊。


这里有两种不同的规则和推论:首先,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 }都起作用。


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

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

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

对于"普通"metod调用,应该使用点和括号。

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


我认为在scala中花括号没有什么特别或复杂的地方。要掌握它们在scala中看似复杂的用法,只需记住以下几点:

  • 大括号形成一个代码块,计算结果是最后一行代码(几乎所有语言都这样做)
  • 如果需要,可以使用代码块生成函数(遵循规则1)
  • 除了case子句(scala选项),一行代码可以省略大括号。
  • 在以代码块作为参数的函数调用中可以省略括号(scala选项)
  • 让我们根据上述三个规则解释几个示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    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)。

    既然这样,我们就可以做到:

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

    最后一个示例只是一个带有三个参数的函数调用,其中每个参数都是首先计算的。

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

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

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

    1
    foo( x => println(x) )

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

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

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

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

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

    现在,我们的代码块包含两个语句,由于在执行foo之前对其进行了评估,所以首先打印"hey",然后将我们的函数传递给foo,"entering foo"将被打印,最后打印"4"。

    这看起来有点难看,scala允许我们跳过括号,这样我们可以写:

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

    1
    foo { x => println(x) }

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


    因为您使用的是case,所以您定义的是分部函数,分部函数需要大括号。


    增加了对parens的编译检查

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    method {
      1 +
      2
      3
    }

    method(
      1 +
      2
      3
     )

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

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

    冗长

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

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

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