首页 文章

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

提问于
浏览
295

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

我从Programming in Scala书中得到的感觉是斯卡拉's pretty flexible and I should use the one I like best, but I find that some cases compile while others don' t .

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

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

=>错误:非法启动简单表达式

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

=>很好 .

8 回答

  • 53

    我试过一次写这个,但最后我放弃了,因为规则有些分散 . 基本上,你必须掌握它 .

    也许最好将注意力集中在花括号和括号可以互换使用的地方:将参数传递给方法调用时 . 如果且仅当方法需要单个参数时,您可以用花括号替换括号 . 例如:

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

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

    增加了对parens的编译检查

    Spray的作者推荐圆形parens因为它们增加了编译检查 . 这对于像Spray这样的DSL尤为重要 . 通过使用parens,您告诉编译器它应该只给出一行;因此,如果你不小心给它两个或更多,它会抱怨 . 现在这不是大括号的情况 - 例如,如果你在某个地方忘记了一个操作符,那么你的代码将被编译,你会得到意想不到的结果,并且可能是一个非常难以找到的bug . 下面是设计的(因为表达式是纯粹的并且至少会给出警告),但是重点是:

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

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

    有人可能认为它与具有默认参数的多参数方法类似;使用parens时,不可能偶然忘记用逗号分隔参数 .

    冗长

    关于冗长的一个经常被忽视的重要说明 . 使用花括号不可避免地导致冗长的代码,因为Scala style guide明确指出关闭花括号必须在他们自己的行上:

    ...闭合括号紧跟在函数的最后一行之后 .

    许多自动重新格式化程序(如IntelliJ)将自动为您执行此重新格式化 . 因此,尽可能坚持使用圆形的parens .

    中缀表示法

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

    另请注意,当您具有多个令牌表达式的单个参数(如 x + 2a => a % 2 == 0 )时,必须使用括号来指示表达式的边界 .

    元组

    因为有时可以省略括号,有时元组需要额外的括号,如 ((1, 2)) ,有时外括号可以省略,如 (1, 2) . 这可能会引起混淆 .

    函数/部分函数文字与大小写

    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语句,声明或表达式 . 它是这样的:

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

    这不是方法调用,因此您无法以任何其他方式编写它 . 那么,您可以在 condition 的括号内放置花括号,并在花括号内使用括号作为代码块:

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

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

  • 4

    这里有几个不同的规则和推论:首先,Scala在a时推断括号参数是一个函数,例如在 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 } 都会 .

  • 13

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

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

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

    对于“正常”的metod调用,您应该使用点和括号 .

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

    我认为值得解释它们在函数调用中的用法以及为什么会发生各种各样的事情 . 正如有人已经说过花括号定义了一个代码块,这也是一个表达式,因此可以放在表达式所在的位置并进行评估 . 在评估时,它的语句被执行,而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类型参数的函数,因此我们可以使用函数文字并将其传递给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) }
    

    要么

    foo { x => println(x) }
    

    这看起来更好,相当于前者 . 这里仍然首先评估代码块,并将评估结果(x => println(x))作为参数传递给foo .

  • -3

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

    因为您正在使用 case ,所以您正在定义部分函数,而部分函数需要花括号 .

  • 6

    Increased compile checking with parens

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

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

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

    有人可能认为它与具有默认参数的多参数方法类似;使用parens时,不可能偶然忘记用逗号分隔参数 .

    Verbosity

    关于冗长的一个经常被忽视的重要说明 . 使用花括号不可避免地导致冗长的代码,因为scala样式指南明确指出必须在自己的行上关闭花括号:http://docs.scala-lang.org/style/declarations.html "... the closing brace is on its own line immediately following the last line of the function."许多自动重新格式化程序(如Intellij)将自动为您执行此重新格式化 . 因此,尽可能坚持使用圆形的parens . 例如 . List(1, 2, 3).reduceLeft{_ + _} 成为:

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

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

相关问题