Scala 中置, 前置, 后置操作符

拟此篇以温习 Scala 对方法调用上的一些约定. 标题中说是关于操作符的事, 其实 Scala 像有了访问方法和属性的一致性原则一样, 可以说操作符与方法更是统一的, 或者说只有方法调用. 此处所称的操作符只不过是 Scala 对无参(prrameterless), 或只有一个参数的方法, 和特殊的四个 unary_+, unary_-, unary_!, unary_~ 方法的便捷的调用约定格式.

一. 中置操作符(对只有一个参数方法的调用约定, a plus b)

case class MyNumber(value: Int) {
  def +(that: MyNumber) = MyNumber(this.value + that.value)
}

调用方式

MyNumber(10).+(MyNumber(20))   //标准调用格式
MyNumber(10) + MyNumber(20)    //只有一个参数时, 不用点, 不用括号

第一行是用点语法的标准方法调用格式, Scala 在当方法只有一个参数时, 可以省略点, 以及括号, 因此可写为上面第二行种的格式. 所以方法 + 就化身为了中置操作符了.

+ 只是一个普通的方法名而已, 因为 Scala 可以用很多符号来定义方法. 所以对于任何的只有一个参数的方法调用可以这么写, 假如 MyNumber 定义了方法

def plus(added: Int) = MyNumber(this.value + added)

应用同样的规则可写成

MyNumber(10) plus 20  //而可不必写成  MyNumber(10).plus(20)

这个约定十分有利于写出 DSL 风格的代码, 例如我们在测试中用 MustMatchers 时这么断言

actualList must have size 10

上面其实就是进行连续了三次是置操作符调用 size, have, 和 must.

除中置操作符外, 另外两种前置与后置操作符又统称为一元操作符, 因为它是不带参数的, 首先来看后置操作符

二. 后置操作符(对于无参数的方法的调用约定, a next)

当函数无参数时, 调用时规则约定其实和只有一个参数时是一样的, 省略点以及括号, 既然一个参数都没有, 所以就只有方法名了. 例子

case class MyNumber(value: Int) {
  def -- = MyNumber(this.value -1)
}

调用方式

MyNumber(10).--  //这是标准格式
MyNumber(10) --  //无参方法的约定格式

由于前面定义的 -- 是无参方法(Parameterless), 所以调用时不能加括号, MyNumber(10).--() 是错误的. 如果是定义的空参数方法(Empty-Paren), 则既可以 MyNumber(10).--() 也可以写成 MyNumber(10) --, 还能 MyNumber(10).--, 但后两者会有警告.

def --() = MyNumber(this.value -1)  //Empty-paren 方法
....
MyNumber(10) --      //Warning: Empty-paren method accessed as parameterless
MyNumber(10).--     //Warning: Empty-paren method accessed as parameterless
MyNumber(10).--()

所以对于后置操作符, 特别是无副作用时应该定义为不带括号的无参方法, 形如  def -- = ....

后置的调用方式很常见, 如

a toString  //而不用写成 a.toString() 或 a.toString
a mkString

三. 前置操作符(+, -, !, ~ 的几个特例方法而已)

Scala 可以实现在操作数前加  +, -, !, ~ 操作符, 如 -a, 所以它们叫做前置(prefix) 操作符. Scala 的做法是把它们分别映射到方法 unary_+, unary_-, unary_!, 和 unary_~. 示例

case class MyNumber(value: Int) {
  def unary_+ = MyNumber(this.value + 1)
}

调用时写成

MyNumber.unary_+  //标准调用格式, 或 MyNumber unary_+
+MyNumber(10)    //实际就调用了 unary_+ 方法

注意, 前置操作只能映射前面四种操作, 想别的都没有. 比如你是否有冲动在 Scala 中实现出  ++a 呢? Scala 是不支持传统的 ++ 递增操作, 后置的 a++ 我们可以用 def ++ = ... 实现. 对于前置的 ++a 如果尝试写成

def unary_++ = MyNumber(this.value + 1)
....
//++MyNumber(10)  //这是无效的, 尝试不会是徒劳的. 只能显式的用下面两种方式调用
MyNumber(10).unary_++
MyNumber(10) unary_++

下面是测试上面三种方式的完整代码

import scala.language.postfixOps

case class MyNumber(value: Int) {

 def +(that: MyNumber) = MyNumber(this.value + that.value)

 def ++ = MyNumber(this.value + 1) //parameterless

 // def -- = MyNumber(this.value - 1) //recommended
 def --() = MyNumber(this.value - 1) //Warning: Empty-paren, not recommended for no side-effect method

 def unary_- = MyNumber(-this.value)
}

object Main extends App {

 println("Infix: " + (MyNumber(10) + MyNumber(20))) //recommended
 println(MyNumber(10).+(MyNumber(20))) //regular method calling style

 println("Postfix1: " + (MyNumber(10) ++)) //recommended
 println(MyNumber(10).++) //regular method calling style

 // MyNumber(10).++() //Compilation error: Application does not take parameters

 println("Postfix2: " + (MyNumber(10) --)) //Warning: Empty-paren method accessed as parameterless
 println((MyNumber(10).--)) //Warning: Empty-paren method accessed as parameterless
 println(MyNumber(10).--()) //regular method calling style

 println("Prefix: " + -MyNumber(10))

 println(MyNumber(10).unary_-)
 println((MyNumber(10) unary_-))
}

上面代码的输出

Infix: MyNumber(30)
MyNumber(30)
Postfix1: MyNumber(11)
MyNumber(11)
Postfix2: MyNumber(9)
MyNumber(9)
MyNumber(9)
Prefix: MyNumber(-10)
MyNumber(-10)
MyNumber(-10)

类别: Scala. 标签: . 阅读(67). 订阅评论. TrackBack.

Leave a Reply

Be the First to Comment!

avatar
wpDiscuz