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

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

= >好。


因为使用的是case,所以定义的是偏函数,而偏函数需要花括号。


社区正在努力标准化大括号和圆括号的使用,参见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")

There are a couple of different rules and inferences going on here: first of all, Scala infers the braces when a parameter is a function, e.g. in list.map(_ * 2) the braces are inferred, it's just a shorter form of list.map({_ * 2}). Secondly, Scala allows you to skip the parentheses on the last parameter list, if that parameter list has one parameter and it is a function, so list.foldLeft(0)(_ + _) can be written as list.foldLeft(0) { _ + _ } (or list.foldLeft(0)({_ + _}) if you want to be extra explicit).

但是,如果添加case,就会得到一个部分函数,而不是其他函数,Scala不会推断部分函数的花括号,所以list。Map (case x => x * 2)将不起作用,但两者都列出。映射({case x => 2 * 2})和列表。映射{case x => x * 2}将。


我曾经试着写过这方面的文章,但最后还是放弃了,因为规则有点分散。基本上,你得掌握窍门。

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

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

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

然而,要更好地掌握这些规则,你还需要了解更多。

增加了使用paren的编译检查

The authors of Spray recommend round parens because they give increased compile checking. This is especially important for DSLs like Spray. By using parens you are telling the compiler that it should only be given a single line; therefore if you accidentally give it two or more, it will complain. Now this isn’t the case with curly braces – if for example you forget an operator somewhere, then your code will compile, and you get unexpected results and potentially a very hard bug to find. Below is contrived (since the expressions are pure and will at least give a warning), but makes the point:

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
)

第一个编译,第二个给出错误:')'期望但发现整数字面量。作者想写1 + 2 + 3。

有人可能会说,这与带默认参数的多参数方法类似;在使用paren时,不可能不小心忘记用逗号分隔参数。

冗长

关于冗长,一个经常被忽视的重要注意事项。使用花括号不可避免地会导致冗长的代码,因为Scala风格指南明确指出,右花括号必须在它们自己的行上:

闭大括号在最后一个大括号后面的另一行上 函数的直线。

许多自动重新格式化程序(如IntelliJ)将自动为您执行这种重新格式化。所以尽量坚持使用圆括号。

中缀表示法

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

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

元组

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

带大小写的函数字面量

Scala有函数和部分函数字面量的语法。它是这样的:

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

你可以使用case语句的唯一其他地方是match和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语句、声明或表达式。它是这样的:

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

如果你需要声明,多条语句,一个导入或者类似的东西,你需要花括号。由于表达式是语句,括号可以出现在花括号内。但有趣的是,代码块也是表达式,所以你可以在表达式中的任何地方使用它们:

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

这不是一个方法调用,所以不能以其他方式编写它。好吧,你可以把花括号放在括号内的条件,以及使用括号在花括号内的代码块:

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

我认为有必要解释一下它们在函数调用中的用法以及为什么会发生各种事情。有人已经说过花括号定义了一个代码块,它也是一个表达式,所以可以放在表达式需要的地方,它将被求值。当被求值时,它的语句被执行,最后的语句值是整个块求值的结果(有点像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类型的参数,因此我们可以使用function literal并将它传递给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) }

or

foo { x => println(x) }

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


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

花括号形成一个代码块,计算到最后一行代码(几乎所有语言都这样做) 如果需要,可以使用代码块生成函数(遵循规则1) 除了case子句外,单行代码可以省略花括号(Scala选择) 括号可以在函数调用中省略,将代码块作为参数(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

增加了使用paren的编译检查

The authors of Spray, recommend that round parens give increased compile checking. This is especially important for DSLs like Spray. By using parens you are telling the compiler that it should only be given a single line, therefore if you accidentally gave it two or more, it will complain. Now this isn't the case with curly braces, if for example, you forget an operator somewhere your code will compile, you get unexpected results and potentially a very hard bug to find. Below is contrived (since the expressions are pure and will at least give a warning), but makes the point

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
 )

第一个编译,第二个给出错误:')'期望但发现整数字面量。作者想写1 + 2 + 3。

有人可能会说,这与带默认参数的多参数方法类似;在使用paren时,不可能不小心忘记用逗号分隔参数。

冗长

An important often overlooked note about verbosity. Using curly braces inevitably leads to verbose code since the scala style guide clearly states that closing curly braces must be on their own line: http://docs.scala-lang.org/style/declarations.html "... the closing brace is on its own line immediately following the last line of the function." Many auto-reformatters, like in Intellij, will automatically perform this reformatting for you. So try to stick to using round parens when you can. E.g. List(1, 2, 3).reduceLeft{_ + _} becomes:

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

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


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