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

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


=>很好。

#1 楼

我曾经尝试写过一次,但是由于规则有些分散,我最终还是放弃了。基本上,您必须掌握它。
也许最好集中精力在花括号和括号可以互换使用的地方:将参数传递给方法调用时。仅在该方法需要单个参数的情况下,才可以用花括号替换括号。例如:
List(1, 2, 3).reduceLeft{_ + _} // valid, single Function2[Int,Int] parameter

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

但是,您还需要了解更多以便更好地掌握这些规则。
使用parens增加编​​译检查
Spray的作者建议使用圆括号,因为它们会增加编译检查。这对于Spray等DSL而言尤其重要。通过使用parens,您是在告诉编译器应该只给它一行。因此,如果您不小心给它两个或更多,它会抱怨。大括号现在不是这种情况–例如,如果您忘记了某个位置的运算符,那么您的代码将被编译,并且您会得到意想不到的结果,并且可能会发现很难发现的错误。下面是人为设计的(因为表达式是纯净的,至少会给出警告),但指出了一点:
method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
)

第一个编译,第二个给出error: ')' expected but integer literal found。作者想写1 + 2 + 3
有人可能会说它与带有默认参数的多参数方法相似;使用parens时,不会偶然忘记用逗号分隔参数。
详细信息
关于冗长性的一个经常被忽略的重要提示。使用大括号不可避免地会导致冗长的代码,因为Scala样式指南明确指出,右大括号必须位于其自己的行上:

…右大括号紧接在最后一行之后的一行上
函数行。

像IntelliJ中一样,许多自动重新格式化程序将自动为您重新格式化。因此,请尽量坚持使用圆括号。
中缀符号
使用infix表示法时,例如List(1,2,3) indexOf (2),如果只有一个参数,则可以省略括号并将其写为List(1, 2, 3) indexOf 2。这不是点符号的情况。
还请注意,当您拥有一个作为多令牌表达式的单个参数(例如x + 2a => a % 2 == 0)时,必须使用括号来表示表达式的边界。 br /> Tuples
因为有时可以省略括号,所以有时像((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 }

这不是方法调用,因此您不能以任何其他方式编写它。好吧,您可以将花括号放在condition的括号内,也可以在花括号内部使用括号作为代码段:
while ({x < 10}) { (x += 1) }

所以,希望对您有所帮助。

评论


这就是为什么人们认为Scala是复杂的。我自称是Scala爱好者。

– andyczerwonka
2012年11月7日20:46

我认为不必为每种方法都引入作用域,这会使Scala代码更简单!理想情况下,任何方法都不应使用{}-一切都应该是一个纯表达式

–最好的
19年2月5日,12:15

@andyczerwonka我完全同意,但这是您为灵活性和表现力所付出的自然而不可避免的价格(?)=> Scala并不过高。对于任何特定情况,这是否是正确的选择当然是另一回事。

–阿什坎Kh。纳扎里
19年6月11日在7:58

您好,当您说List {1、2、3} .reduceLeft(_ + _)无效时,您是说它的语法错误吗?但是我发现代码可以编译。我把我的代码放在这里

–卡尔文
19年8月1日在4:52

在所有示例中都使用了List(1、2、3),而不是List {1、2、3}。 Sc,在Scala的当前版本(2.13)上,此操作失败,并显示其他错误消息(意外的逗号)。您可能必须回到2.7或2.8才能得到原始错误。

–丹尼尔·C·索布拉尔
19年8月2日在8:30

#2 楼

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

评论


不仅是最后一个参数列表。例如,list.foldLeft {0} {_ + _}起作用。

–丹尼尔·C·索布拉尔
2010-12-8 11:28

啊,我确定我已经读到它只是最后一个参数列表,但显然我错了!很高兴知道。

– Theo
2010年12月8日,12:55

#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)说,您应该仅对操作符之类的方法(例如+,-)使用infix,而不要对诸如takeWhile之类的常规方法使用。缀标记的全部要点是允许DSL和自定义运营商,因此不应在此上下文中始终使用它。

–最好的
2014年12月29日上午8:51

#4 楼

我认为Scala中的花括号没有什么特别或复杂的地方。要掌握它们在Scala中看似复杂的用法,请牢记以下几件事:


大括号形成代码块,其结果为代码的最后一行(几乎所有语言都这样做)
如果需要,可以用代码块生成一个函数(遵循规则1)
单行代码的大括号可以省略,除了case子句(标量选择) )
可以在以代码块作为参数的函数调用中省略括号(选择标度)

根据上述三个规则来解释几个示例:

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

–爱
15年9月24日在17:47

@aij,感谢您的评论。对于1,我建议Scala提供对{}行为的熟悉程度。我已经更新了措辞,以求精确。对于4,由于()和{}之间的相互作用,这有点棘手,因为def f(x:Int):Int = f {x}起作用,这就是为什么我获得第5名。 :)

– lcn
2015年9月24日20:18在

我倾向于认为()和{}在Scala中几乎可以互换,只是它对内容的解析不同。我通常不写f({x}),所以f {x}不会像用curly那样代替括号。其他语言实际上确实允许您省略parethese,例如,fun f(x)= f x在SML中有效。

–爱
2015年9月26日14:34在

@aij,将f {x}视为f({x})似乎对我来说是一个更好的解释,因为()和{}可互换性的考虑不太直观。顺便说一下,f({x})的解释在一定程度上受Scala规范(第6.6节)的支持:ArgumentExprs :: =``('[Exprs]’)'| ‘(([Exprs’,’] PostfixExpr‘:’‘_’‘*’’)’| [nl] BlockExp

– lcn
2015年9月27日,下午2:45

#5 楼

我认为有必要解释它们在函数调用中的用法以及发生各种事情的原因。正如有人已经说过的,花括号定义了一个代码块,它也是一个表达式,因此可以放在期望表达式的地方并对其进行求值。进行评估时,将执行其语句,而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,打印“ Entering foo”,最后打印“ 4”。

但是,这看起来有点丑陋,Scala让我们跳过了括号,因此我们可以这样写:

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




foo { x => println(x) }


看起来好多了,并且等效于以前的版本。这里仍然首先对代码块求值,并将求值结果(x => println(x))作为参数传递给foo。

评论


只是我一个。但实际上,我确实更喜欢foo({x => println(x)})的显式性质。也许我束手无策...

–亲爱的
16年5月12日在1:04

#6 楼

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

评论


我通常要求一个答案,而不仅仅是这个示例的答案。

–Marc-François
2012年8月22日14:20

#7 楼

使用parens增强编译检查

Spray的作者建议使用圆括号来增强编译检查。这对于Spray等DSL而言尤其重要。通过使用parens,您是在告诉编译器应该只给它一行,因此,如果您不小心给了它两行或更多行,它将抱怨。现在大括号不是这种情况,例如,如果您忘记了要在代码中编译的运算符,则会得到意想不到的结果,并且可能会发现很难发现的错误。下面是人为设计的(因为表达式是纯净的,至少会给出警告),但要指出一点。第一个编译,第二个给出作者想要的error: ')' expected but integer literal found.要写1 + 2 + 3

有人可能会说它与带有默认参数的多参数方法相似;使用parens时,可能会偶然忘记逗号分隔参数。

详细程度

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

method {
  1 +
  2
  3
}

method(
  1 +
  2
  3
 )


#8 楼

理想的编码样式中的括号基本上用于单行代码。
但是如果特定代码段是多行,则使用花括号是一种更好的方法。

#9 楼

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