Scala 魔法函数

Scala有一些语法糖,让一些特定名称的函数拥有一些特殊的能力。这些语法糖并没有专门的文档介绍,只是散落在不同的文章和教程中。本文整理里这些魔法函数,并通过例子演示它们的功能。

apply, unapply

当类或对象有一个主要用途的时候,apply方法为你提供了一个很好的语法糖。比如a是一个对象, a.apply(x)则可以简化为a(x)
unapply方法是抽取器(Extractor),有人也翻译成提取器,经常用在模式匹配上。

1
2
3
4
5
6
7
8
9
10
11
val foo = Foo(1)
foo match {
case Foo(x) => println(x)
}
class Foo(val x: Int) {}
object Foo {
def apply(x: Int) = new Foo(x)
def unapply(f: Foo) = Some(f.x)
}

我们一般都在伴生对象内定义apply,unapply方法,但是Trait,class内都可以定义这些方法。

update

apply方法类似,update也是一个魔法方法。对于一个实例a, Scala可以将a.update(x,y)简化为a(x)=y

1
2
3
4
5
6
7
8
9
10
11
12
13
14
val bar = Bar(10)
bar(0) = 1
println(bar(0))
class Bar(n :Int) {
val a = Array[Int](n)
def apply(n :Int) = a(n)
def update(n:Int, v:Int) = a(n) = v
}
object Bar {
def apply(n :Int) = new Bar(n)
}

val bar = Bar(10)调用伴生对象的apply方法生成一个Bar的实例。bar(0) = 1等价于bar.update(0,1)println(bar(0))等价于println(bar.apply(0)),也就是class Bar中定义的apply方法。

unapplySeq

类似unapply,用来从对象中抽取序列,常用在模式匹配上:

1
2
3
4
5
6
7
8
9
10
11
12
val m = M(1)
m match {
case M(n1,others@_*) => println(others)
}
class M {}
object M {
def apply(a: Int*): M = new M
def unapplySeq(m: M): Option[Seq[Int]] = Some(1 :: 2 :: 3 :: Nil)
}

identifier_=

如果你只是简单的字段操作,只需定义一个var类型的属性即可。但是如果你在set的时候执行额外的逻辑,比如参数检查等,你就可能需要setter符号。
Scala为setter提供了特殊的语法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
val obj = new X
obj.x = -1
println(obj.x)
obj.x = 10
println(obj.x)
class X {
var _x = 0
//setter
def x_=(n: Int) {
if (n < 0) _x = 0 else _x = n
}
//getter
def x = _x
}

上面的例子中x_=就是x的setter。

一元操作符

1
2
3
4
unary_+
unary_-
unary_!
unary_~

如果你想重载/实现一元操作符(+,-,!,~),可以定义上面的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
val x = new X('小')
println(!x)
x.s = '黑'
println(!x)
class X(var s: Char) {
val s1 = "大小多少长短胖瘦高矮黑白冷暖"
def unary_! = {
var i = s1 indexOf s
if (i % 2 == 0) i = i + 1 else i = i - 1
new X(s1(i))
}
override def toString = "X(" + s + ")"
}

op=

如果你定义了一个操作符op,那么A <op>= B能自动转换为A = A <op> B

1
2
3
4
5
6
7
8
9
10
11
12
13
var s = new X("abc")
s ::/ 1
println(s)
s ::/= 1
println(s)
class X(var s: String) {
def ::/(n:Int) = {
new X(s + n)
}
override def toString = "X(" + s + ")"
}

操作符不能是字母和数字(alphanumeric),也不能是!=, ==, <= 或者 >=。

Dynamic

Dynamic对象可以调用不存在的字段或者方法,Scala将它们转换为下面的四个方法:

1
2
3
4
selectDynamic
updateDynamic
applyDynamic
applyDynamicNamed

这有点动态语言的风格。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
val d = new DynImpl
println(d.foo)
d.foo = 10
println(d.ints(1, 2, 3))
println(d.ints(i1 = 1, i2 = 2, 3))
class DynImpl extends Dynamic {
var map = Map.empty[String, Any]
def selectDynamic(name: String) = name
def updateDynamic(name: String)(value: Any) {
map += name -> value
}
def applyDynamic(name: String)(args: Any*) = s"method '$name' called with arguments ${args.mkString("'", "', '", "'")}"
def applyDynamicNamed(name: String)(args: (String, Any)*) = s"method '$name' called with arguments ${args.mkString("'", "', '", "'")}"
}

_*

_*可以用作模式匹配任意数量的参数:

1
2
3
4
5
6
case class A(n: Int*)
val a = A(1, 2, 3, 4, 5)
a match {
case A(1, 2, _*) =>
}

你可以将可变参数绑定到一个值上:

1
2
3
a match {
case A(1, 2, as @_*) => println(as)
}

另外_*还可以作为辅助类型描述:

1
2
val l = 1 :: 2 :: 3 :: Nil
val a2 = A(l :_*)

这里:_*用来将集合作为可变参数传递。

@ Pattern Binders

Scala规范8.1.3定义了模式绑定的定义。上面的例子as @_*也演示了这个功能。下面再看个例子:

1
2
3
4
5
6
7
8
9
sealed abstract class Expr
case class Number(num: Double) extends Expr
case class UnOp(operator: String, arg: Expr) extends Expr
val expr = UnOp("abs", Number(-1))
expr match {
case UnOp("abs", e @ Number(_)) => println(e)
case _ =>
}

参考资料

  1. http://stackoverflow.com/questions/1483212/list-of-scalas-magic-functions
  2. http://stackoverflow.com/questions/15799811/how-does-type-dynamic-work-and-how-to-use-it
  3. http://www.scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html#pattern-binders