首页 文章

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

提问于
浏览
95

什么时候可以省略(省略)括号,圆点,大括号,=(函数)等的精确规则?

例如,

(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) 必须等于(2)导致:

“未找到: Value 第一”

然而, (service.findAllPresentations.get.first.votes.size) 的"must be equalTo 2"必须等于2,也就是说,方法链接工作正常? - 对象链链链接 .

我查看了Scala的书籍和网站,无法找到全面的解释 .

实际上,正如Rob H在Stack Overflow问题Which characters can I omit in Scala?中解释的那样,省略'.'的唯一有效用例是"operand operator operand"样式操作,而不是方法链接?

6 回答

  • 37

    你似乎偶然发现了答案 . 无论如何,我会尽力说清楚 .

    使用前缀,中缀和后缀表示法时可省略点 - 所谓的运算符表示法 . 使用运算符表示法时,只有这样,如果传递给方法的参数少于两个,则可以省略括号 .

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

    我将简要介绍一下这些符号 .

    Prefix:

    只有 ~!+- 可用于前缀表示法 . 这是您在编写 !flagval liability = -debt 时使用的符号 .

    Infix:

    这是方法出现在对象和它的参数之间的符号 . 算术运算符都适合这里 .

    Postfix (also suffix):

    当方法跟随对象 and receives no parameters 时使用该表示法 . 例如,您可以编写 list tail ,这是后缀表示法 .

    只要没有方法,就可以毫无问题地链接中缀符号 . 例如,我喜欢使用以下样式:

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

    这与以下内容相同:

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

    现在,为什么我在这里使用括号,如果filter和map只使用一个参数?这是因为我将匿名函数传递给他们 . 我不能将匿名函数定义与中缀样式混合,因为我需要一个边界来结束我的匿名函数 . 此外,匿名函数的参数定义可能被解释为中缀方法的最后一个参数 .

    您可以使用具有多个参数的中缀:

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

    使用中缀表示法很难使用Curried函数 . 折叠功能就是一个明显的例子:

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

    您需要在中缀调用之外使用括号 . 我不确定这里的确切规则 .

    现在,我们来谈谈postfix . Postfix可能很难使用,因为除了表达式的结尾之外,它永远不会被使用 . 例如,您无法执行以下操作:

    list tail map (...)
    

    因为尾部没有出现在表达式的末尾 . 你不能这样做:

    list tail length
    

    您可以使用括号来标记表达式的结尾来使用中缀表示法:

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

    请注意,不建议使用后缀表示法,因为it may be unsafe .

    我希望这已经消除了所有的疑虑 . 如果没有,只需发表评论,我就会看到我可以做些什么来改进它 .

  • 12

    class 定义:

    valvar 可以从类参数中省略,这将使参数变为私有 .

    添加var或val将导致它是公共的(即生成方法访问器和更改器) .

    如果该类没有正文,则可以省略 {} ,即

    class EmptyClass
    

    类实例化:

    如果可以由编译器推断出通用参数,则可以省略它们 . 但请注意,如果您的类型不匹配,则始终输入type参数以使其匹配 . 因此,如果没有指定类型,您可能无法得到您所期望的 - 也就是说,给定

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

    这将给你一个类型错误(找到Int,期望String)

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

    虽然这很好用:

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

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


    函数定义:

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

    {} 如果是函数体可以删除function是单个语句,但仅当语句返回一个值(你需要 = 符号)时,即

    def returnAString = "Hi!"
    

    但这不起作用:

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

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

    如果函数不带任何参数,则可以删除 () ,即

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

    按照惯例,它被保留用于没有副作用的方法 - 稍后会更多 .

    () 在定义pass by name参数时实际上并没有被删除,但它实际上是一个语义上不同的表示法,即

    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's answer .

    • . 也可以删除后缀函数列表尾部

    可以为后缀运算符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,过度使用丢弃的返回类型可能对重用代码有害 . 只需查看规范,就可以了解由于代码中缺少显式信息而导致可读性降低的好例子 . 实际计算出变量类型的间接层次数可以是坚果 . 希望更好的工具可以避免这个问题,并保持我们的代码简洁 .

    (好的,为了编写一个更完整,更简洁的答案(如果我错过了任何内容,或者得到了错误/不准确请注释),我已经添加到答案的开头 . 请注意这不是一种语言规范,所以我不是想让它在学术上完全正确 - 更像是参考卡 . )

  • 2

    一系列报价,可以深入了解各种情况......

    就个人而言,我认为规范中还有更多内容 . 我肯定一定有,我只是不寻找合适的词......

    然而,有几个来源,我已经收集了它们,但没有真正完整/全面/可理解的/向我解释上述问题......:

    “如果一个方法体有一个以上的表达式,你必须用花括号包围它 . 如果方法体只有一个表达式,你可以省略括号 . ”

    来自chapter 2, "Type Less, Do More", of Programming Scala

    “上层方法的主体出现在等号'='之后 . 为什么等号?为什么不只是花括号,就像在Java中一样?因为分号,函数返回类型,方法参数列表,甚至是卷曲大括号有时会被省略,使用等号可以防止几种可能的解析歧义 . 使用等号也提醒我们,甚至函数都是Scala中的值,这与Scala对函数式编程的支持是一致的,在第8章“函数式编程”中有更详细的描述 . 在斯卡拉 . “

    来自chapter 1, "Zero to Sixty: Introducing Scala", of Programming Scala

    “没有参数的函数可以在没有括号的情况下声明,在这种情况下,必须使用no来调用它括弧 . 这提供了对统一访问原则的支持,使得调用者不知道符号是变量还是没有参数的函数 . 如果函数体返回一个值(即返回类型不是Unit),则函数体前面带有“=”,但当类型为Unit时,返回类型和“=”可以省略(即它看起来像一个过程而不是一个功能) . 身体周围的大括号不是必需的(如果身体是单个表情);更确切地说,函数体只是一个表达式,任何包含多个部分的表达式都必须用括号括起来(一个部分的表达式可以选择用括号括起来 . )“没有参数的函数可以调用圆点和圆括号 . 但是任何表达式都可以在它周围加上括号,所以你可以省略点并仍然使用括号 . 由于您可以在任何可以使用括号的地方使用大括号,因此您可以省略点并放入大括号,其中可以包含多个语句 . 没有参数的函数可以在没有括号的情况下调用 . 例如,String上的length()函数可以调用为“abc”.length而不是“abc”.length() . 如果函数是没有括号定义的Scala函数,则必须在没有括号的情况下调用该函数 . 按照惯例,使用括号调用没有带副作用的参数的函数,例如println;那些没有副作用的人被称为没有括号 . “

    来自博客文章Scala Syntax Primer

    “过程定义是一个函数定义,其中省略了结果类型和等号;它的定义表达式必须是一个块 . 例如,def f(ps)等价于def f(ps):Unit = . 例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
    

    再次,(Int)是一个在类中声明的vanilla方法(这里实际上有一些隐式类型转换,但是你得到了漂移) . “

    来自Scala for Java Refugees Part 6: Getting Over Java

    “现在,当你尝试”m 0“时,Scala抛弃它是一个一元运算符,理由是它不是有效的(〜,!, - 和) . 它发现”m“是一个有效的对象 - 它是一个函数,而不是一个方法,并且所有函数都是对象 . 由于“0”不是有效的Scala标识符,它既不能是中缀也不能是后缀运算符 . 因此,Scala抱怨它期望“;” - 哪个将两个(几乎)有效的表达式分开:“m”和“0” . 如果你插入它,那么它会抱怨m需要一个参数,或者,如果失败,则“_”将它变成一个部分应用的函数“ . “我相信只有当您在左侧有一个显式对象时,运算符语法风格才有效 . 语法旨在让您以自然的方式表达”操作数运算符操作数“样式操作 . ”

    Which characters can I omit in Scala?

    但令我困惑的是这句话:

    “需要有一个对象来接收方法调用 . 例如,你不能做”println“Hello World!”“因为println需要一个对象接收者 . 你可以做“Console println”Hello World!“”满足需要 . “

    因为据我所见,有一个对象接收电话......

  • 82

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

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

    正如博客文章中所述:http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-6 .

    所以也许这实际上是一个非常严格的"syntax sugar",它只适用于你在一个对象上有效调用方法的地方,它接受一个参数 . 例如

    1 + 2
    1.+(2)
    

    没有别的 .

    这可以解释我在问题中的例子 .

    但正如我所说的,如果有人能指出确切地指出了语言规范的哪个位置,那将非常感激 .

    好吧,一些好人(来自#scala的paulp_)指出了语言规范中的这个信息是:

    6.12.3:运算符的优先级和关联性决定了表达式各部分的分组,如下所示 . 如果表达式中有多个中缀操作,则具有较高优先级的运算符会更紧密地绑定比优先级较低的运营商 . 如果有连续的中缀操作e0 op1 e1 op2 . . .opn en与运算符op1 ,. . . ,具有相同优先级的opn,则所有这些运算符必须具有相同的关联性 . 如果所有运算符都是左关联的,则序列被解释为(...(e0 op1 e1)op2 ...)opn en . 否则,如果所有运算符都是正确关联的,则序列被解释为e0 op1(e1 op2(... .en en)...) . 后缀运算符的优先级始终低于中缀运算符 . 例如 . 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是一个新名称 .

    嗯 - 对我而言,它与我所看到的不相符,或者我只是不理解它;)

  • 1

    没有 . 您可能会收到有关该功能是否有副作用的建议 . 这是假的 . 更正是在Scala允许的合理范围内不使用副作用 . 在某种程度上它不能,那么所有的赌注都是关闭的 . All 投注 . 使用括号是集合"all"的一个元素,并且是多余的 . 所有投注结束后,它不提供任何 Value .

    这个建议本质上是一个失败的尝试(不要混淆:没有其他效果系统有用) .

    尽量不要副作用 . 在那之后,接受所有赌注都已关闭 . 隐藏在效果系统的事实上的句法符号背后可以而且确实只会造成伤害 .

  • 2

    我发现遵循这个经验法则更容易:在表达式中,空格在方法和参数之间交替 . 在您的示例中, (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 .

相关问题