What is the formal difference in Scala between braces and parentheses, and when should they be used?
将参数传递给括号
我从scala书中的编程中得到的感觉是scala非常灵活,我应该使用我最喜欢的,但是我发现有些情况是编译的,而其他情况则不编译。
例如(只是作为一个例子;我很感谢任何讨论一般情况的回答,而不仅仅是这个特定的例子):
1 2 |
=>错误:简单表达式的非法开头
=罚款。
我曾经试过写这篇文章,但最后我放弃了,因为规则有些漫不经心。基本上,你必须掌握它的窍门。好的。
也许最好集中在大括号和括号可以互换使用的地方:当向方法调用传递参数时。如果且仅当方法需要一个参数时,可以用大括号替换括号。例如:好的。
1 2 3 | List(1, 2, 3).reduceLeft{_ + _} // valid, single Function2[Int,Int] parameter List{1, 2, 3}.reduceLeft(_ + _) // invalid, A* vararg parameter |
但是,为了更好地掌握这些规则,您还需要了解更多。好的。增加了对parens的编译检查
喷雾的作者推荐圆括号,因为它们增加了编译检查。这对于DSLS类喷雾尤为重要。通过使用parens,您告诉编译器应该只给它一行;因此,如果您不小心给它两行或更多行,它会抱怨。现在,大括号不是这样的——例如,如果在某个地方忘记了一个运算符,那么您的代码将被编译,您会得到意想不到的结果,并且可能很难找到一个bug。下面是人为的(因为表达式是纯的,并且至少会给出警告),但重点是:好的。
1 2 3 4 5 6 7 8 9 10 11 | method { 1 + 2 3 } method( 1 + 2 3 ) |
第一个编译,第二个给出
有人可能会说,对于具有默认参数的多参数方法,这是相似的;在使用parens时,不可能意外地忘记逗号来分隔参数。好的。冗长
关于冗长的一个重要的经常被忽视的注释。使用大括号不可避免地会导致冗长的代码,因为scala样式指南清楚地指出关闭大括号必须在它们自己的行上:好的。
… the closing brace is on its own line immediately following the last
line of the function.Ok.
许多自动重新格式化,如intellij中,都会自动为您执行这种重新格式化。所以尽量坚持使用圆帕伦斯。好的。中缀表示法
当使用中缀表示法时,如
还要注意,当您有一个单参数是多标记表达式时,如
因为有时可以省略括号,有时元组需要额外的括号,就像在
scala有函数和部分函数文本的语法。看起来是这样的:好的。
您唯一可以使用
1 2 3 4 5 6 7 8 |
您不能在任何其他上下文中使用
括号可用于生成子表达式。大括号可以用来制作代码块(这不是一个函数文本,所以要小心尝试像使用一个一样使用它)。代码块由多个语句组成,每个语句都可以是导入语句、声明或表达式。就像这样:好的。
1 2 3 4 5 6 7 8 9 10 |
所以,如果需要声明、多个语句、一个
因此,由于表达式是语句,代码块是表达式,下面的所有内容都是有效的:好的。
1 2 3 4 5 6 | 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... |
不能互换的地方
基本上,你不能用
1 |
这不是方法调用,因此不能以任何其他方式编写。好吧,您可以在
1 |
所以,我希望这有帮助。好的。好啊。
这里有两种不同的规则和推论:首先,scala在参数是函数时推断大括号,例如在
但是,如果你加上
社区正在努力规范大括号和圆括号的使用,请参阅scala样式指南(第21页):http://www.codecommit.com/scala-style-guide.pdf
高阶方法调用的建议语法是始终使用大括号,并跳过点:
对于"普通"metod调用,应该使用点和括号。
1 |
我认为在scala中花括号没有什么特别或复杂的地方。要掌握它们在scala中看似复杂的用法,只需记住以下几点:
让我们根据上述三个规则解释几个示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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 |
我认为有必要解释它们在函数调用中的用法以及为什么会发生各种各样的事情。正如有人所说,大括号定义了一个代码块,它也是一个表达式,因此可以将表达式放在需要表达式的地方,并对其进行计算。当被评估时,执行它的语句,Last的语句值是整个块评估的结果(有点像Ruby)。
既然这样,我们就可以做到:
1 2 3 | 2 + { 3 } // res: Int = 5 val x = { 4 } // res: x: Int = 4 List({1},{2},{3}) // res: List[Int] = List(1,2,3) |
最后一个示例只是一个带有三个参数的函数调用,其中每个参数都是首先计算的。
现在来看一下它如何处理函数调用,让我们定义一个简单的函数,它将另一个函数作为参数。
1 |
要调用它,我们需要传递接受一个int类型参数的函数,因此我们可以使用函数literal并将其传递给foo:
1 | foo( x => println(x) ) |
如前所述,我们可以使用代码块代替表达式,所以让我们使用它
1 | foo({ x => println(x) }) |
这里发生的是对内的代码进行评估,函数值作为块评估的值返回,然后将该值传递给foo。这在语义上与前面的调用相同。
但我们可以添加更多内容:
1 | foo({ println("Hey"); x => println(x) }) |
现在,我们的代码块包含两个语句,由于在执行foo之前对其进行了评估,所以首先打印"hey",然后将我们的函数传递给foo,"entering foo"将被打印,最后打印"4"。
这看起来有点难看,scala允许我们跳过括号,这样我们可以写:
1 | foo { println("Hey"); x => println(x) } |
或
1 | foo { x => println(x) } |
看起来好多了,和以前的差不多。这里仍然先计算代码块,然后将计算结果(即x=>println(x))作为参数传递给foo。
因为您使用的是
增加了对parens的编译检查
喷雾的作者,建议圆括号给予增加的编译检查。这对于DSLS类喷雾尤为重要。通过使用parens,您可以告诉编译器应该只给它一行,因此如果您不小心给了它两行或更多行,它会抱怨。现在大括号不是这样的,例如,如果在代码将要编译的某个地方忘记了一个运算符,就会得到意想不到的结果,并且可能很难找到一个bug。下面是人为的(因为表达式是纯的,并且至少会给出警告),但说明了这一点
1 2 3 4 5 6 7 8 9 10 11 | method { 1 + 2 3 } method( 1 + 2 3 ) |
第一个汇编,第二个给出了作者想要写的
有人可能会说,对于具有默认参数的多参数方法,这是相似的;在使用parens时,不可能意外地忘记逗号来分隔参数。
冗长
关于冗长的一个重要的经常被忽视的注释。使用大括号不可避免地会导致冗长的代码,因为scala样式指南清楚地指出关闭大括号必须在它们自己的行上:http://docs.scala-lang.org/style/declarations.html"…右大括号紧跟在函数的最后一行之后,在它自己的行上。"许多自动重新格式化,如intellij中,将自动为您执行这种重新格式化。所以尽量坚持使用圆帕伦斯。例如,
1 2 3 | List(1, 2, 3).reduceLeft { _ + _ } |
使用大括号,您得到的是分号,而不是括号。考虑到