在Scala中,大括号和圆括号在形式上有什么区别?什么时候应该使用它们?

czfnxgou  于 2022-11-23  发布在  Scala
关注(0)|答案(9)|浏览(142)

用括号()和大括号{}传递参数给函数在形式上有什么区别?
我从 * Programming in Scala * 书中得到的感觉是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 }

好的。

ogq8wdun

ogq8wdun1#

我曾经试着写过这方面的文章,但最后我放弃了,因为规则有点分散。基本上,你必须掌握它的窍门。
也许最好集中在花括号和圆括号可以互换使用的地方:传递参数给方法呼叫时。当且仅当方法需要单一参数时,您 * 可以 * 将大括号取代为括号。例如:

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

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

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

使用括号增加编译检查

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

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
)

第一个编译,第二个给出error: ')' expected but integer literal found。作者想写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保留字搭配使用:
第一个

  • 您不能在任何其他上下文中使用case语句 *。因此,如果要使用case,您 * 需要 * 花括号。如果您想知道函数和部分函数文字之间的区别,答案是:如果Scala需要一个函数,那么你得到的就是一个函数;如果Scala需要一个partial函数,那么你得到的就是一个partial函数;如果两者都需要,那么它会给出一个关于歧义的错误。

表达式和块

圆括号可以用来构造子表达式。大括号可以用来构造代码块(这 * 不是 * 函数文字,所以要注意不要把它当作函数文字来使用)。一个代码块由多个语句组成,每个语句都可以是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

因此,由于表达式是语句,而代码块是表达式,因此下面的一切都是有效的:

不可互换

基本上,在其他任何地方都不能用()替换{},反之亦然。

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

这不是一个方法调用,所以你不能用任何其他方式编写它。好吧,你可以在condition的括号内 * 放入花括号,也可以在代码块的花括号内 * 放入圆括号:

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

所以,我希望这对你有帮助。

whhtz7ly

whhtz7ly2#

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

liwlm1x9

liwlm1x93#

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

chhkpiq44#

我不认为Scala中的花括号有什么特别或复杂的地方。要掌握它们在Scala中看似复杂的用法,只需记住几件简单的事情:
1.花括号形成一个代码块,它计算到代码的最后一行(几乎所有语言都这样做)
1.如果需要,可以使用代码块生成函数(遵循规则1)
1.除了case子句(Scala选项)外,单行代码中可以省略花括号
1.在使用代码块作为参数的函数调用中可以省略括号(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
8qgya5xd

8qgya5xd5#

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

cyvaqqii

cyvaqqii6#

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

kg7wmglp

kg7wmglp7#

使用括号增加编译检查

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

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
 )

第一个编译,第二个给出作者想要写的1 + 2 + 3
有人可能会说,对于带有默认参数的多参数方法,情况也是如此;在使用括号时不可能不小心忘记用逗号分隔参数。

详细程度

一个经常被忽视的关于冗长的重要注意事项。使用花括号不可避免地导致冗长的代码,因为scala风格指南明确指出,结束花括号必须在它们自己的行上:http://docs.scala-lang.org/style/declarations.html“...右大括号在函数的最后一行之后。”许多自动重新格式化程序(如Intellij)会自动为您执行这种重新格式化。因此,在可能的情况下尽量使用圆括号。例如,List(1, 2, 3).reduceLeft{_ + _}变为:

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

sirbozc58#

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

gwbalxhn

gwbalxhn9#

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

相关问题