什么时候可以省略括号,点,花括号,=(函数)等的确切规则是什么?


106

何时可以省略(省略)括号,点,花括号,=(函数)等的确切规则是什么?

例如,

(service.findAllPresentations.get.first.votes.size) must be equalTo(2).
  • service 是我的对象
  • def findAllPresentations: Option[List[Presentation]]
  • votes 退货 List[Vote]
  • 必须有规格的两种功能

我为什么不能去:

(service findAllPresentations get first votes size) must be equalTo(2)

编译器错误是:

“类型为Option [List [com.sharca.Presentation]]的RestServicesSpecTest.this.service.findAllPresentations不接受参数”

为什么会认为我要传递参数?为什么在每个方法调用中都必须使用点?

为什么必须(service.findAllPresentations get first votes size)equalTo(2)导致:

“未找到:价值至上”

但是,“必须等于2” (service.findAllPresentations.get.first.votes.size)必须等于2,也就是说,方法链接可以正常工作吗?-对象链链链参数。

我浏览了Scala的书和网站,却找不到真正的全面解释。

实际上,正如Rob H在Stack Overflow问题中解释的那样,我可以在Scala中省略哪些字符?,这是省略“。”的唯一有效用例。是用于“操作数运算符操作数”样式的操作,而不是方法链接?

Answers:


87

您似乎偶然发现了答案。无论如何,我会尽力弄清楚。

使用前缀,中缀和后缀表示法(即所谓的运算符)时,可以省略点。仅当使用运算符表示法时,如果传递给该方法的参数少于两个,则可以省略括号。

现在,运算符表示法是方法调用的表示,这意味着在没有被调用对象的情况下不能使用它。

我将简要介绍这些符号。

字首:

只有~!+并且-可以在前缀符号使用。这是编写!flag或时使用的表示法val liability = -debt

中缀:

这就是该方法出现在对象及其参数之间的表示法。算术运算符都适合这里。

后缀(也有后缀):

当该方法跟随对象并且不接收任何参数时,将使用该表示法。例如,您可以编写list tail,这是后缀表示法。

只要不使用任何方法,就可以毫无问题地链接中缀符号调用。例如,我喜欢使用以下样式:

(list
 filter (...)
 map (...)
 mkString ", "
)

这与以下内容相同:

list filter (...) map (...) mkString ", "

现在,如果filter和map采用单个参数,为什么还要在此处使用括号?这是因为我正在将匿名函数传递给他们。我不能将匿名函数定义与infix样式混合使用,因为我的匿名函数的末尾需要一个边界。另外,匿名函数的参数定义可能会解释为infix方法的最后一个参数。

您可以将infix与多个参数一起使用:

string substring (start, end) map (_ toInt) mkString ("<", ", ", ">")

带有括号的函数很难使用咖喱函数。折叠功能就是一个明显的例子:

(0 /: list) ((cnt, string) => cnt + string.size)
(list foldLeft 0) ((cnt, string) => cnt + string.size)

您需要在infix调用之外使用括号。我不确定确切的规则在这里发挥作用。

现在,让我们谈谈后缀。后缀可能很难使用,因为除了表达式的末尾,永远无法在任何地方使用。例如,您不能执行以下操作:

 list tail map (...)

因为tail不会出现在表达式的末尾。您也不能这样做:

 list tail length

您可以通过在括号中标记表达式的结尾来使用中止符号:

 (list tail) map (...)
 (list tail) length

请注意,不建议使用后缀表示法,因为它可能不安全

我希望这消除了所有疑问。如果没有,请发表评论,我会做些改进。


嗯,所以你在我的声明中说:((((((realService findAllPresentations)get)first)vote)size)必须等于2-get,first,vote和size都是后缀运算符,因为它们不带参数?所以我想知道必须,等于和等于是什么...
安东尼·斯塔布斯2009年

我什么也没说,尽管我很确定一定是这样。:-)“ be”可能是一个帮助对象,可以使语法更漂亮。或者,更具体地讲,是要使中缀符号与“必须”一起使用。
Daniel C. Sobral

get是Option.get,First是list.first,投票是case类属性,大小是list.size。您现在怎么看?
安东尼·斯塔布斯

嗯,是的-“ {realService findPresentation 1).get.id必须等于1”这一事实进一步证明了这一点-因为Service#findPresentations(id:Int)似乎是一个infix运算符。酷-我想我明白了。:)
安东尼·斯塔布斯2009年

42

类定义:

valvar可以从类参数中省略,这会将参数设为私有。

添加var或val将使其公开(即,生成方法访问器和变异器)。

{} 如果该类没有主体,则可以省略,即

class EmptyClass

类实例化:

如果通用参数可以由编译器推断,则可以省略。但是请注意,如果您的类型不匹配,则总是会推断出type参数以使其匹配。因此,如果不指定类型,则可能无法获得期望的结果-即

class D[T](val x:T, val y:T);

这会给你一个类型错误(发现整数,预期的字符串)

var zz = new D[String]("Hi1", 1) // type error

而这很好用:

var z = new D("Hi1", 1)
== D{def x: Any; def y: Any}

因为类型参数T被推断为两者中最不常见的超类型-Any。


函数定义:

= 如果函数返回Unit(无),则可以将其删除。

{}如果函数是单个语句,则可以删除函数主体,但前提是该语句返回一个值(您需要使用=符号),即,

def returnAString = "Hi!"

但这不起作用:

def returnAString "Hi!" // Compile error - '=' expected but string literal found."

如果可以推断出函数的返回类型,则可以忽略它(递归方法必须指定其返回类型)。

() 如果函数不接受任何参数,则可以将其删除,也就是说,

def endOfString {
  return "myDog".substring(2,1)
}

按照惯例,它保留给没有副作用的方法-稍后再介绍。

()实际上,在定义“ 按名称传递”参数时实际上并没有删除它,但实际上是一个语义上完全不同的符号,即,

def myOp(passByNameString: => String)

说myOp接受了一个pass-by-name参数,该参数导致一个String(即它可以是一个返回字符串的代码块)与函数参数相对,

def myOp(functionParam: () => String)

这表示myOp需要一个具有零参数的函数并返回一个String。

(请记住,传递名称的参数会编译为函数;这只会使语法更好。)

() 如果函数仅接受一个参数,则可以将其放在函数参数定义中,例如:

def myOp2(passByNameString:(Int) => String) { .. } // - You can drop the ()
def myOp2(passByNameString:Int => String) { .. }

但是,如果需要多个参数,则必须包含():

def myOp2(passByNameString:(Int, String) => String) { .. }

声明:

.可以删除以使用运算符表示法,该运算符只能用于中缀运算符(采用参数的方法的运算符)。有关更多信息,请参见Daniel的答案

  • . 也可以删除后缀功能列表尾部

  • () 可以删除后缀运算符list.tail

  • () 不能与以下定义的方法一起使用:

    def aMethod = "hi!" // Missing () on method definition
    aMethod // Works
    aMethod() // Compile error when calling method
    

因为此表示法是按惯例保留给没有副作用的方法(如List#tail)使用的(即,调用没有副作用的函数意味着该函数除返回值外没有任何可观察的效果)。

  • () 传入单个参数时可以删除运算符符号

  • () 可能需要使用不在语句末尾的后缀运算符

  • () 可能需要指定嵌套语句,匿名函数的末尾或使用多个参数的运算符

调用带有函数的函数时,不能从内部函数定义中省略(),例如:

def myOp3(paramFunc0:() => String) {
    println(paramFunc0)
}
myOp3(() => "myop3") // Works
myOp3(=> "myop3") // Doesn't work

当调用带有by-name参数的函数时,不能将参数指定为无参数的匿名函数。例如,给定:

def myOp2(passByNameString:Int => String) {
  println(passByNameString)
}

您必须将其称为:

myOp("myop3")

要么

myOp({
  val source = sourceProvider.source
  val p = myObject.findNameFromSource(source)
  p
})

但不是:

myOp(() => "myop3") // Doesn't work

IMO,过度使用丢弃返回类型对于重新使用代码可能有害。只看一下规范,这是由于代码中缺少显式信息而导致降低可读性的一个好例子。实际上可以弄清楚变量类型是什么的间接级别数可以说是胡说八道。希望有更好的工具可以避免这个问题,并使我们的代码简洁。

(好的,为了寻求一个更完整,简洁的答案(如果我错过了任何内容,或者遇到了一些错误/不正确的地方,请发表评论),我已经在答案的开头添加了。请注意,这不是一种语言规范,因此我并没有试图使其在学术上完全正确-就像参考卡一样。)


10
我在哭 这是什么。
Profpatsch

12

一组报价可以深入了解各种情况...

我个人认为,规范中还会有更多内容。我敢肯定一定有,我只是没有在寻找正确的词...

但是,有两个来源,我将它们收集在一起,但是没有什么真正完整/全面/可理解的/可以向我解释以上问题...:

“如果方法主体具有多个表达式,则必须用花括号{…}将其括起来。如果方法主体只有一个表达式,则可以省略花括号。”

Scala编程的第2章“少输入,多输入”

“上层方法的主体位于等号'='之后。为什么要等号?为什么不像Java中那样使用花括号{…}呢?因为分号,函数返回类型,方法参数列表甚至花括号有时会被省略,使用等号可以防止几种可能的语法歧义;使用等号还提醒我们,即使函数也是Scala中的值,这与Scala对函数式编程的支持相一致,这在第8章,函数式编程中有更详细的描述。 Scala。”

Scala 编程的第1章“零到六十:Scala简介”

“可以在不带括号的情况下声明不带参数的函数,在这种情况下,必须在不带括号的情况下调用它。这提供了对统一访问原则的支持,这样,调用者就不知道符号是变量还是不带符号的函数。参数。

如果函数体返回值(例如,返回类型是除Unit以外的其他值),则在函数体前面加上“ =”,但是当类型为Unit时(例如,它看起来像一个过程),可以省略返回类型和“ =”而不是功能)。

不需要在身体周围有大括号(如果身体是单个表情);更准确地说,函数的主体只是一个表达式,任何具有多个部分的表达式都必须用大括号括起来(一个部分的表达式可以选择用大括号括起来)。”

“具有零个或一个参数的函数可以在不带点和括号的情况下调用。但是任何表达式都可以在其周围带有括号,因此您可以省略点并仍然使用括号。

由于可以在任何可以使用括号的地方使用括号,因此可以省略点并放在括号中,括号可以包含多个语句。

没有参数的函数可以不带括号而被调用。例如,可以将String上的length()函数调用为“ abc” .length,而不是“ abc” .length()。如果该函数是不带括号定义的Scala函数,则必须在不带括号的情况下调用该函数。

按照惯例,不带参数且具有副作用的函数(例如println)用括号调用;没有副作用的称为无括号。”

从博客文章Scala Syntax Primer

“过程定义是一个函数定义,其中省略了结果类型和等号;其定义表达式必须是一个块。例如,def f(ps){stats}等同于def f(ps):Unit = {stats }。

示例4.6.3这是一个名为write的过程的声明和定义:

trait Writer {
    def write(str: String)
}
object Terminal extends Writer {
    def write(str: String) { System.out.println(str) }
}

上面的代码隐式地完成了以下代码:

trait Writer {
    def write(str: String): Unit
}
object Terminal extends Writer {
    def write(str: String): Unit = { System.out.println(str) }
}"

根据语言规范:

“通过仅采用单个参数的方法,Scala允许开发人员使用空格替换。并省略括号,从而启用插入运算符示例中所示的运算符语法。该语法在Scala API的其他地方使用,例如作为构建Range实例:

val firstTen:Range = 0 to 9

再一次,to(Int)是在类内声明的一种香草方法(实际上,这里有一些隐式的类型转换,但会产生漂移)。”

Scala for Java难民(第6部分):克服Java

“现在,当您尝试使用“ m 0”时,Scala会以不是有效的运算符(〜,!,-和+)为由而舍弃它作为一元运算符。它会发现“ m”是有效的对象-它是一个函数,而不是方法,并且所有函数都是对象。

由于“ 0”不是有效的Scala标识符,因此它既不能是中缀,也不能是后缀运算符。因此,Scala抱怨它期望“;” -将两个(几乎)有效表达式分开:“ m”和“ 0”。如果插入它,那么它将抱怨m需要一个参数,否则,将返回“ _”以将其转换为部分应用的函数。”

“我相信只有当您在左侧有一个显式对象时,运算符语法样式才能起作用。该语法旨在让您以自然的方式表示“ operand运算符操作数”样式的操作。”

我可以在Scala中省略哪些字符?

但是,这句话也让我感到困惑:

“必须有一个对象才能接收方法调用。例如,您不能执行“ println“ Hello World!””,因为println需要对象接收者。您可以执行“ Console println“ Hello World!”来满足需要。

因为据我所看到的,一个对象接收呼叫...


1
好的,因此请尝试阅读Specs来源以获得一些线索和麻烦。这是魔术代码问题的一个很好的例子-太多的混合,类型推断以及隐式转换和隐式参数。从外面很难理解!对于像这样的大型图书馆,更好的工具可能会产生一些奇迹……有一天……
Antony Stubbs

3

我发现遵循此经验法则更容易:在表达式中,方法和参数之间可以有空格交替。在您的示例中,(service.findAllPresentations.get.first.votes.size) must be equalTo(2)解析为(service.findAllPresentations.get.first.votes.size).must(be)(equalTo(2))。请注意,2周围的括号具有比空格更高的关联性。点也具有较高的关联性,因此(service.findAllPresentations.get.first.votes.size) must be.equalTo(2)将解析为(service.findAllPresentations.get.first.votes.size).must(be.equalTo(2))

service findAllPresentations get first votes size must be equalTo 2解析为service.findAllPresentations(get).first(votes).size(must).be(equalTo).2


2

实际上,在二读时,也许这是关键:

通过仅采用单个参数的方法,Scala允许开发人员替换。用空格并省略括号

如网志文章所述:http : //www.codecommit.com/blog/scala/scala-for-java-refugees-part-6

因此,也许这实际上是一个非常严格的“语法糖”,它在您有效地调用带有一个参数的对象上的方法时才起作用。例如

1 + 2
1.+(2)

没别的。

这将解释我在问题中的示例。

但是正如我所说,如果有人指出语言规范中的确切位置,将不胜感激。

好的,一些好人(来自#scala的paulp_)指出了该信息在语言规范中的位置:

6.12.3:运算符的优先级和关联性决定表达式部分的分组,如下所示。

  • 如果表达式中有多个中缀运算,则优先级较高的运算符将比优先级较低的运算符更紧密地绑定。
  • 如果有连续的中缀操作e0 op1 e1 op2。。.opn与运算符op1,。。。,优先级相同,则所有这些运算符必须具有相同的关联性。如果所有运算符都是左关联的,则该序列将被解释为(..((e0 op1 e1)op2 ....)opn en。否则,如果所有运算符都是右关联的,则该序列将解释为e0 op1(e1 op2(..opn))。
  • Postfix运算符的优先级始终低于infix运算符。例如,e1 op1 e2 op2始终等于(e1 op1 e2)op2。

左关联运算符的右侧操作数可以由括在括号中的几个参数组成,例如e op(e1,...,en)。然后将此表达式解释为e.op(e1,...,en)。

左关联二进制运算e1 op e2解释为e1.op(e2)。如果op是右关联的,则将相同的操作解释为{val x = e1; e2.op(x)},其中x是新名称。

嗯-对我来说,它与我所看到的不符,或者我只是听不懂;)


嗯,进一步加剧混乱,这也是有效的:(((((((realService findAllPresentations)get)first)选民)票)size)必须等于2,但如果我删除任何一对括号对,则不行……
安东尼Stubbs

2

没有啊 您可能会收到有关该功能是否有副作用的建议。这是假的。纠正措施是不要在Scala允许的合理范围内使用副作用。在一定程度上,所有赌注都没有了。所有赌注。使用括号是集合“ all”的一个元素,并且是多余的。一旦取消所有下注,它就不会提供任何价值。

该建议本质上是失效的效果系统的尝试(不要与以下效果系统混淆:效果不如其他效果系统)。

尽量不要产生副作用。之后,请接受所有下注。隐藏在效果系统的事实上的句法符号后面,只会而且确实会造成伤害。


是的,但是当您使用混合OO /功能语言时,这就是问题所在吗?在任何实际的示例中,您都将需要具有副作用功能...您能为我们指出一些有关“效果系统”的信息吗?我认为,更重要的引号是“可以在不带括号的情况下声明不带参数的函数,在这种情况下,必须在不带括号的情况下调用该函数。这为统一访问原则提供了支持,因此调用方不知道是否符号是没有参数的变量或函数。”。
安东尼·斯塔布斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.