scala动态特性与宏编程

scala动态特性

像ruby、groovy等语言都有一种动态(dynamic)的东西(如对象调用它的所述类并未创建的方法),它们属于各自语言元编程的一部分,由于它们本身就是动态弱语言,包含这些东西并不奇怪。scala是静态的强类型语言,而且所有的类型系统在编译时就要确定的,这个特性似乎比较难实现,但sip17提出了加入此功能的建议,scala团队也完成了这个,他们是通过编译器对这些代码重编码,

先直接看个例子吧:


 

class WrapperMapper[M](map: mutable.Map[String,M]) extends Dynamic{



def selectDynamic(name:String):Option[M] = map.get(name)



def updateDynamic(name:String)(value:M): Option[M] = map.put(name,value)



}



val i = new WrapperMapper[Int](mutable.Map("love" -> 2))

println(i.love)

i.hate = 5

println(i.hate)

WrapperMapper并没有love和hate属性或方法,但依然可以调用,这是为什么呢?scala的编译器在使用WrapperMapper这个类的对象的地方,如果发现它的对象调用了本不存在的方法、属性等等,就会给它加工一下,让他变成selectDynamic\updateDynamic等等方法,而本来的方法名变成相应的参数,具体的对应策略如下:

*{{{

* foo.method("blah") ~~> foo.applyDynamic("method")("blah")

* foo.method(x = "blah") ~~> foo.applyDynamicNamed("method")(("x", "blah"))

* foo.method(x = 1, 2) ~~> foo.applyDynamicNamed("method")(("x", 1), ("", 2))

* foo.field ~~> foo.selectDynamic("field")

* foo.varia = 10 ~~> foo.updateDynamic("varia")(10)

* foo.arr(10) = 13 ~~> foo.selectDynamic("arr").update(10, 13)

* foo.arr(10) ~~> foo.applyDynamic("arr")(10)

* }}}

注意,可以动态调用的类必须是继承这个特质:Dynamic,而且必须导入这个变量:scala.language.dynamics,否则编译器会编译出错(或者编译时加上一个参数:-language:dynamics)

再来看scala的宏编程(macro),这个才是scala的元编程。

大家都应该用过java的反射,scala是java的衍生语言,自然也有反射,而且它还有一种更高级的反射,就是编译时反射,它就是宏。

宏在scala是实验性产品,尚未纳入标准库中去,但不阻碍我们去使用。不过这个是有点复杂,而且ide容易报错(但实际并没有错),而且编译时有些代码需要分别编译否则会报错,所以上手很容易出错,先来看个例子:

object Debug {



def apply[T](x: => T):T = macro impl

def impl(c:blackbox.Context)(x:c.Tree) = {

import c.universe._

val q"..$stats" = x

val loggedStats = stats.flatMap {stat =>



val msg = "executing " + showCode(stat)

List(q"println(${msg})",stat)

}

q"..$loggedStats"

}



}

使用部分:

val n = Debug {

val a = 1

val b = a + 2

a + b

}

println(n)

 

debug有个apply方法,它的方法体是macro impl,也就是宏指向impl,而impl是个宏方法(我自己命名的),它接受两个参数,第一个是一个blackbox.Context,在2.10版是Context,2.11版有两个,一个是blackbox和whitebox,这两个我不花大篇幅的讲(因为我不太能讲清楚),反正这里都能用,后者用处比前者稍广一些,但scala对前者使用比较有信心,并会先将他放进标准库中。第二个参数是一个c.Tree的,它代表着原方法的参数。

再来看一个神奇的例子:

object TestImpl {

def _println[T:c.WeakTypeTag](c:blackbox.Context)(cond:c.Tree) = {

import c.universe._

// val Literal(Constant(v:Int)) = cond.tree

//这样写编译时就会打印,而不是运行时

q"""${println(cond)}"""

}

}



class PrintA[T] {

def myPrint(cond:T):Unit = macro TestImpl._println[T]

}

 

这个是我第一次测试macro写的例子,使用部分:

val printA = new PrintA[Int]

printA.myPrint(2)

当我在编译时我发现编译期间就打印了2,运行时却没有,真是太神奇了,这说明了一点,编译器它先运行了这个宏内的代码,再把q中的结果填到原来使用的该方法的地方去。q叫做“quasiquotes”,很多拥有宏编程的都有这个东东,我对宏编程不算很了解,但这里有一点我是知道的,这个quasiquotes可以取出code,而这里的${}部分会先进行计算在包含到这个code中去,scala编译器再把这个code放到调用的地方去,然后再去编译,c语言也有宏,了解它的应该知道宏可以预编译,然后在汇编时把这个预编译的代码放到调用的源码中,比如#define PI = 3.14,然后在每个用到PI的地方它做了个替换工作,而不是变量引用。scala的宏做了差不多的工作。我们吧使用部分稍作修改下:

val printA = new PrintA[Int]

val x = 2

printA.myPrint(x+2)

在编译是会打印x.+(2),是不是很有意思?这说明这个传进的c.Tree也是个表达式(其实也是个"quasiquotes")。当然这个结果不是我们想要的,所以做点修改,将_println方法改成这样子;

def _println[T:c.WeakTypeTag](c:blackbox.Context)(cond:c.Tree) = {

import c.universe._

// val Literal(Constant(v:Int)) = cond.tree

//这样写编译时就会打印,而不是运行时

// q"""${println(cond)}"""

//这样才起作用

q"""println($cond)"""

}

现在在来分析第一个例子,首先通过解构,拿到表达式的每一条语句(都是一个tree),然后通过flatMap将一条语句变成两条,第一条是打印语句表达式,第二个就是原语句不变,所以在使用部分的代码变成如下形式:

println("val a = 1")

val a = 1

println("val b = a.+(2)")

val b = a + 2

println("a.+(b)")

a + b

scala的宏我能介绍的差不多就这些了,还有一些特性,如宏注解、类型提供者等等,我也在研究中,并思考其用途。

转载于:https://my.oschina.net/mylgb634335272/blog/889738

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值