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

发布于 2024-10-06 06:59:21 字数 464 浏览 6 评论 0原文

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

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

=>美好的。

What is the formal difference between passing arguments to functions in parentheses () and in braces {}?

The feeling I got from the Programming in Scala book is that Scala's pretty flexible and I should use the one I like best, but I find that some cases compile while others don't.

For instance (just meant as an example; I would appreciate any response that discusses the general case, not this particular example only):

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

=> error: illegal start of simple expression

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

=> fine.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

何处潇湘 2024-10-13 06:59:21

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

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

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: ')' 预期但找到整数文字。作者想写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 }

这不是一个方法调用,所以你不能用任何其他方式编写它。那么,您可以将大括号内部放在条件的括号内,也可以将括号内部用于代码块的大括号内:

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

I tried once to write about this, but I gave up in the end, as the rules are somewhat diffuse. Basically, you’ll have to get the hang of it.

Perhaps it is best to concentrate on where curly braces and parentheses can be used interchangeably: when passing parameters to method calls. You may replace curly braces with parentheses if, and only if, the method expects a single parameter. For example:

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

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

However, there’s more you need to know to better grasp these rules.

Increased compile checking with parens

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
)

The first compiles, the second gives error: ')' expected but integer literal found. The author wanted to write 1 + 2 + 3.

One could argue it’s similar for multi-parameter methods with default arguments; it’s impossible to accidentally forget a comma to separate parameters when using parens.

Verbosity

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:

… 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.

Infix Notation

When using infix notation, like List(1,2,3) indexOf (2) you can omit parentheses if there is only one parameter and write it as List(1, 2, 3) indexOf 2. This is not the case of dot-notation.

Note also that when you have a single parameter that is a multi-token expression, like x + 2 or a => a % 2 == 0, you have to use parentheses to indicate the boundaries of the expression.

Tuples

Because you can omit parentheses sometimes, sometimes a tuple needs extra parentheses like in ((1, 2)), and sometimes the outer parentheses can be omitted, like in (1, 2). This may cause confusion.

Function/Partial Function literals with case

Scala has a syntax for function and partial function literals. It looks like this:

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

The only other places where you can use case statements are with the match and catch keywords:

object match {
    case pattern if guard => statements
    case pattern => statements
}
try {
    block
} catch {
    case pattern if guard => statements
    case pattern => statements
} finally {
    block
}

You cannot use case statements in any other context. So, if you want to use case, you need curly braces. In case you are wondering what makes the distinction between a function and partial function literal, the answer is: context. If Scala expects a function, a function you get. If it expects a partial function, you get a partial function. If both are expected, it gives an error about ambiguity.

Expressions and Blocks

Parentheses can be used to make subexpressions. Curly braces can be used to make blocks of code (this is not a function literal, so beware of trying to use it like one). A block of code consists of multiple statements, each of which can be an import statement, a declaration or an expression. It goes like this:

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

So, if you need declarations, multiple statements, an import or anything like that, you need curly braces. And because an expression is a statement, parentheses may appear inside curly braces. But the interesting thing is that blocks of code are also expressions, so you can use them anywhere inside an expression:

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

So, since expressions are statements, and blocks of codes are expressions, everything below is valid:

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...

Where they are not interchangeable

Basically, you can’t replace {} with () or vice versa anywhere else. For example:

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

This is not a method call, so you can’t write it in any other way. Well, you can put curly braces inside the parentheses for the condition, as well as use parentheses inside the curly braces for the block of code:

while ({x < 10}) { (x += 1) }
ゞ花落谁相伴 2024-10-13 06:59:21

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

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

However, if you add case you get, as others have mentioned, a partial function instead of a function, and Scala will not infer the braces for partial functions, so list.map(case x => x * 2) won't work, but both list.map({case x => 2 * 2}) and list.map { case x => x * 2 } will.

最舍不得你 2024-10-13 06:59:21

社区正在努力标准化大括号和括号的使用,请参阅 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 is an effort from the community to standardize the usage of braces and parentheses, see Scala Style Guide (page 21): http://www.codecommit.com/scala-style-guide.pdf

The recommended syntax for higher order methods calls is to always use braces, and to skip the dot:

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

For "normal" metod calls you should use the dot and parentheses.

val result = myInstance.foo(5, "Hello")
这个俗人 2024-10-13 06:59:21

我不认为 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

I don't think there is anything particular or complex about curly braces in Scala. To master the seeming-complex usage of them in Scala, just keep a couple of simple things in mind:

  1. curly braces form a block of code, which evaluates to the last line of code (almost all languages do this)
  2. a function if desired can be generated with the block of code (follows rule 1)
  3. curly braces can be omitted for one-line code except for a case clause (Scala choice)
  4. parentheses can be omitted in function call with code block as a parameter (Scala choice)

Let's explain a couple of examples per the above three rules:

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
骄兵必败 2024-10-13 06:59:21

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

or

foo { x => println(x) }

这看起来更好,并且与前面的等效。这里仍然首先评估代码块,并将评估结果(即 x => println(x))作为参数传递给 foo。

I think it is worth explaining their usage in function calls and why various things happen. As someone already said curly braces define a block of code, which is also an expression so can be put where expression is expected and it will be evaluated. When evaluated, its statements are executed and last's statement value is the result of whole block evaluation (somewhat like in Ruby).

Having that we can do things like:

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

Last example is just a function call with three parameters, of which each is evaluated first.

Now to see how it works with function calls let's define simple function that take another function as a parameter.

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

To call it, we need to pass function that takes one param of type Int, so we can use function literal and pass it to foo:

foo( x => println(x) )

Now as said before we can use block of code in place of an expression so let's use it

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

What happens here is that code inside {} is evaluated, and the function value is returned as a value of the block evaluation, this value is then passed to foo. This is semantically the same as previous call.

But we can add something more:

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

Now our code block contains two statements, and because it is evaluated before foo is executed, what happens is that first "Hey" is printed, then our function is passed to foo, "Entering foo" is printed and lastly "4" is printed.

This looks a bit ugly though and Scala lets us to skip the parenthesis in this case, so we can write:

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

or

foo { x => println(x) }

That looks much nicer and is equivalent to the former ones. Here still block of code is evaluated first and the result of evaluation (which is x => println(x)) is passed as an argument to foo.

空心↖ 2024-10-13 06:59:21

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

Because you are using case, you are defining a partial function and partial functions require curly braces.

摇划花蜜的午后 2024-10-13 06:59:21

使用括号增强编译检查

Spray 的作者建议使用圆括号增强编译检查。这对于像 Spray 这样的 DSL 尤为重要。通过使用括号,您告诉编译器应该只给出一行,因此,如果您不小心给了它两行或更多行,它会抱怨。现在,花括号的情况并非如此,例如,如果您在代码将编译的地方忘记了运算符,您会得到意外的结果,并且可能会出现很难找到的错误。下面是人为的(因为表达式是纯的,至少会给出警告),但表明了这一点:

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
 )

第一个编译,第二个给出 error: ')' 预期的错误,但找到了整数文字。 作者想要写成1 + 2 + 3

有人可能会说这与具有默认参数的多参数方法类似;使用括号时不可能意外忘记用逗号来分隔参数。

冗长

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

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

Increased compile checking with parens

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
 )

The first compiles, the second gives error: ')' expected but integer literal found. the author wanted to write 1 + 2 + 3.

One could argue it's similar for multi-parameter methods with default arguments; it's impossible to accidentally forget a comma to separate parameters when using parens.

Verbosity

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 {
  _ + _
}
风筝在阴天搁浅。 2024-10-13 06:59:21

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

Parenthesis in an ideal coding style is basically used for single line code.
But if the particular piece of code is multiline then using braces is a better way.

已下线请稍等 2024-10-13 06:59:21

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

With braces, you got semicolon induced for you and parentheses not. Consider takeWhile function, since it expects partial function, only {case xxx => ??? } is valid definition instead of parentheses around case expression.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文