【问题标题】:What is the formal difference in Scala between braces and parentheses, and when should they be used?Scala 中大括号和圆括号之间的形式区别是什么,什么时候应该使用它们?
【发布时间】:2011-05-22 03:07:36
【问题描述】:

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

我从 Programming in Scala 一书中得到的感觉是 Scala 非常灵活,我应该使用我最喜欢的那个,但我发现有些案例可以编译,而其他案例则不能。 p>

例如(仅作为示例;我将不胜感激任何讨论一般情况的回复,而不仅仅是这个特定示例):

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

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

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

=> 很好。

【问题讨论】:

    标签: scala syntax parentheses braces


    【解决方案1】:

    我曾经尝试过写这篇文章,但最后我放弃了,因为规则有些分散。基本上,你必须掌握它。

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

    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: ')' expected but integer literal found。作者想写1 + 2 + 3

    人们可能会争辩说,对于具有默认参数的多参数方法来说,它是相似的;使用括号时,不可能不小心忘记用逗号分隔参数。

    冗长

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

    …右大括号在最后一行之后 函数行。

    许多自动重新格式化程序(例如 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 }
    

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

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

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

    【讨论】:

    • 这就是人们认为 Scala 复杂的原因。我称自己为 Scala 爱好者。
    • 不必为我认为使 Scala 代码更简单的每种方法都引入范围!理想情况下,任何方法都不应该使用{} - 一切都应该是一个单一的纯表达式
    • @andyczerwonka 我完全同意,但这是您为灵活性和表达能力付出的自然和不可避免的代价(?)=> Scala 并没有被高估。对于任何特定情况,这是否是正确的选择当然是另一回事。
    • 你好,当你说List{1, 2, 3}.reduceLeft(_ + _)无效时,你的意思是它有语法错误吗?但我发现代码可以编译。我把我的代码here
    • 您在所有示例中都使用了List(1, 2, 3),而不是List{1, 2, 3}。唉,在 Scala 的当前版本(2.13)上,这会失败并显示不同的错误消息(意外的逗号)。您可能必须回到 2.7 或 2.8 才能得到原来的错误。
    【解决方案2】:

    这里有几个不同的规则和推论:首先,当参数是函数时,Scala 会推断大括号,例如在list.map(_ * 2) 中推断出大括号,它只是list.map({_ * 2}) 的更短形式。其次,Scala允许您跳过最后一个参数列表上的括号,如果该参数列表有一个参数并且它是一个函数,那么list.foldLeft(0)(_ + _)可以写成list.foldLeft(0) { _ + _ }(或者list.foldLeft(0)({_ + _}),如果你想额外明确的)。

    但是,如果您添加 case,正如其他人所提到的,您会得到一个偏函数而不是函数,并且 Scala 不会推断偏函数的大括号,因此 list.map(case x =&gt; x * 2) 将不起作用,但 @ 987654328@ 和list.map { case x =&gt; x * 2 } 将。

    【讨论】:

    • 不仅是最后一个参数列表。例如,list.foldLeft{0}{_+_} 有效。
    • 啊,我确定我读过它只是最后一个参数列表,但显然我错了!很高兴知道。
    【解决方案3】:

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

    【讨论】:

    • 实际上约定是使用圆括号,该链接是非官方的。这是因为在函数式编程中,所有函数都只是一阶公民,因此不应区别对待。其次,Martin Odersky 说您应该尝试只对类似运算符的方法使用中缀(例如 +--),而不是像 takeWhile 这样的常规方法。中缀符号的全部意义在于允许 DSL 和自定义运算符,因此不应一直在这种情况下使用它。
    【解决方案4】:

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

    【讨论】:

    • 1.实际上并非在所有语言中都是正确的。 4. 在 Scala 中实际上并非如此。例如:def f(x:Int) = f x
    • @aij,感谢您的评论。对于 1,我建议 Scala 为 {} 行为提供熟悉度。我已经更新了精确的措辞。而对于 4,由于 (){} 之间的交互,这有点棘手,因为 def f(x: Int): Int = f {x} 有效,这就是我获得第 5 个的原因。 :)
    • 我倾向于认为 () 和 {} 在 Scala 中大部分是可以互换的,只是它以不同的方式解析内容。我通常不写 f({x}) 所以 f{x} 不想省略括号,而是用花括号代替它们。其他语言确实允许您省略括号,例如,fun f(x) = f x 在 SML 中有效。
    • @aij,将f {x} 视为f({x}) 对我来说似乎是一个更好的解释,因为考虑(){} 可互换不太直观.顺便说一句,f({x}) 解释在某种程度上得到了 Scala 规范(第 6.6 节)的支持:ArgumentExprs ::= ‘(’ [Exprs] ‘)’ | ‘(’ [Exprs ‘,’] PostfixExpr ‘:’ ‘_’ ‘*’ ’)’ | [nl] BlockExp
    【解决方案5】:

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

    foo { x => println(x) }
    

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

    【讨论】:

    • 只有我一个。但我实际上更喜欢foo({ x =&gt; println(x) }) 的明确性质。也许我太拘泥于自己的方式......
    【解决方案6】:

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

    【讨论】:

    • 我要求一个一般性的答案,而不仅仅是这个例子的答案。
    【解决方案7】:

    增加了带括号的编译检查

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

    method {
      1 +
      2
      3
    }
    
    method(
      1 +
      2
      3
     )
    

    第一个编译,第二个给error: ')' expected but integer literal found.作者想写的1 + 2 + 3

    人们可能会争辩说,对于具有默认参数的多参数方法来说,它是相似的;使用括号时,不可能不小心忘记用逗号分隔参数。

    冗长

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

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

    【讨论】:

      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

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

        【讨论】:

          猜你喜欢
          • 2018-10-13
          • 2016-06-26
          • 1970-01-01
          • 1970-01-01
          • 2012-04-05
          • 1970-01-01
          • 2013-04-01
          相关资源
          最近更新 更多