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 def x_=(n: Int) { if (n < 0) _x = 0 else _x = n } 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 _ => }
|
参考资料
- http://stackoverflow.com/questions/1483212/list-of-scalas-magic-functions
- http://stackoverflow.com/questions/15799811/how-does-type-dynamic-work-and-how-to-use-it
- http://www.scala-lang.org/files/archive/spec/2.11/08-pattern-matching.html#pattern-binders