从Java和JavaScript来学习Haskell和Groovy(元编程)
本篇文章的话题是元编程。首先来认识元编程,我在第一篇《引子》里面已经介绍:元编程,指的是在运行时改变“类”的定义,例如访问、增加或修改等等。一言以蔽之,就是“用程序来写程序”。在第二篇的《类型系统》里面已经借由继承和接口的实现,介绍了一些利用元编程特性来增加或改变子类行为的方法。回顾语言发展的长河,其实是经历了一个从“对象 -> 类 -> 元类”到“对象 -> 原型”的发展过程的。所以,无论是类,还是元类,这样的概念其实都不是非有不可的,只是因为我们思考的习惯,特别是抽象的习惯而顺其自然地产生了。这一点我在《编程范型:工具的选择》里面已经详细描述了,建议在往下阅读前移步。
正式介入元编程的部分,先来看看Java,它的方式比较原始,也比较清晰,本身它定义了Class、Method、Field等等描述一个类的基本概念,基于静态语言的限制,没有办法真正在运行时改变一个类内部的结构(但是可以在运行时获取一个类内部的结构),于是有了像CGLib这样在运行时使用动态代理,创建一个类来替代的办法,让使用者看起来好像是改变了原始类的结构。当然,在编译期,像AspectJ这样的工具可以做到真正的“织入”逻辑,控制字节码的生成。对于Java的元编程本身而言,即便到今天,局限性很大,但是局限性并不意味着有用性,可以说如今元编程的应用已经铺天盖地,其中有这样两件事情大大加速了它元编程的发展:
一是JDK 5的注解,虽说它和元编程本身没有直接的联系,但是它提供了一种便捷的代码修饰方式,也让对于既有代码的扩展变得方便而充满可能。比如像Lombok这样基于注解的类库,让一个类的扩展和完善非常容易。
二是Spring,无论是学J2ME还是J2EE,Spring都是值得去了解的,AOP的概念老早就提了,但就是从它开始发扬光大的;IoC,把对象管理和拼装的逻辑反转到业务逻辑之外的容器上,这些实现都是需要通过对元编程的操纵来完成的。
再来看看Haskell,把它和Java放在一起介绍,因为二者都是静态语言,改变类或者定义结构的事情只能寄期望于编译期完成。Haskell的元编程并非核心内容,因此也更加初级,据我所知,基本上谈及Haskell的元编程,必谈Template Haskell(TH)。我对TH的了解属于刚接触,对于进一步了解,需要知晓这样两个概念,抽象语法树(abstract syntax tree,AST),代码语法分析成功以后就会生成AST,它包含的内容和代码本身是一致的。而TH的执行结果,也是生成一棵AST。
接着要了解的概念是QuasiQuotation,里面可以存放任何字符串,被视作一个表达式,允许程序员写自定义的结构片段(下面的中括号组合加上里面的竖线的这个结构 [| |])。比如(例子来自这里):
silly :: QuasiQuoter silly = QuasiQuoter { quoteExp = \_ -> [| "yeah!!!" |] }
这一篇介绍相对比较容易理解(里面还介绍了使用reify来自省)。比如 [d|head’ (x:_) = x|] 这样的表达式会被解析成为这样一棵AST:[FunD head’ [Clause [InfixP (VarP x_1) GHC.Types.: WildP] (NormalB (VarE x_1)) []]]。
如果表达式里面还有Quotation,就需要使用 $() 来区分,比如:
emptyShow :: Name -> Q [Dec] emptyShow name = [d|instance Show $(conT name) where show _ = ""|]
无论是上面的哪一个,限制都还是太多了,主要的原因还是在于,它们是静态语言;因此要用元编程用得自如,必须深入学习一门动态语言。
来看JavaScript。从静态语言的囚笼中解脱出来, JavaScript的元编程的能力虽然强大,但是却很容易归纳:
对对象的自省,对对象方法和属性的改变,这里的对象既包括普通的对象和方法实例,也包括prototype这个特殊成员;
eval关键字。
其余那些元编程的特性,都是其他人或者说第三方基于以上元编程基本的能力给后加上去的。
对于第一条,其实可以用下面这个最简单的例子来概括:
function Func(){}; var func = new Func(); func.a = function(){ console.log("a"); }; Func.b = function(){ console.log("b"); }; Func.prototype.c = function(){ console.log("c"); }; // instance func.a(); // function Func.b(); // prototype func.c();
而对于第二条,还是用一个最简单的例子来说明,数据和代码等价的道理(还有一个关于模板引擎使用代码生成的例子在这里):
var str = "{a:3, b:4}"; var obj = eval("(" + str + ")"); console.log(obj);
最后是Groovy,把Groovy放在最后是因为它的元编程特性太丰富了(下面的特性,如果要找例子都可以去这个官网的链接)。Java的所有元编程能力全部保留,在之基础上,下面我有选择地介绍几条。
1、MethodMissing:这是一个我非常喜欢的特性,简言之就是当被调用方法不存在时,可以执行的自定义方法,想一想,这相当于为对象提供了一个重要的特性:default行为。与methodMissing相对的,还有propertyMissing。
class Foo { def methodMissing(String name, def args) { return "this is me" } } assert new Foo().someUnknownMethod(42l) == 'this is me'
2、GroovyInterceptable:这个特性是给方法调用增加一层拦截逻辑,换句话说,是AOP的一种实现,比如:
class SimplePOGO implements GroovyInterceptable { void targetMethod(){ System.out.println("...") } def invokeMethod(String name, args){ System.out.println("${name} is being called") //Get the method that was originally called. def calledMethod = SimplePOGO.metaClass.getMetaMethod(name, args) calledMethod?.invoke(this, args) } } simplePogo = new SimplePOGO() simplePogo.targetMethod()
上面的例子调用targetMethod,但是拦截逻辑放在invokeMethod里面。
3、Categories,这是个从Objective C搬过来的特性。这个怎么说呢,很像电脑游戏里面角色的隐藏技能,平时不具备,但是危急关头(使用use关键字)可以触发打开,等到危急结束(use的代码块结束),技能又消失,恢复原状。
use(TimeCategory) { println 1.minute.from.now println 10.hours.ago def someDate = new Date() println someDate - 3.months }
4、Magic Package。“魔法包”?听起来就很牛的样子,对吧。如果我们遵循magic package的命名规约,我们可以创建自定义的元类(MetaClass):
groovy.runtime.metaclass.[package].[class]MetaClass
比如我们要改变java.lang.String的逻辑,那就实现一个MetaClass,并且这个类的路径是:
groovy.runtime.metaclass.java.lang.StringMetaClass
BTW,Groovy的MetaClass的一系列子类能力很强,连static method之类的东西都可以改变。更多的元编程特性,去官网找就好了。
但是回过头来看一下,若论功能和特性的种类和纷繁程度,自然没得说,但是从语言设计的简洁性来说,JavaScript这个老被说“有缺陷”的语言却可以甩Groovy几条街。这并非一个孰好孰坏的评判,正如同接口的设计一样,有人喜欢最简接口,有人喜欢人本接口。有的语言就是喜欢简洁,像我以前提过的Io语言,连关键字都省了;但是像Perl呢,却说:
There’s More Than One Way To Do It.
所以,程序员啊,开心最重要了。(-_-)~…
建议继续学习:
- MySQL小工具 之 压测Groovy (阅读:2333)
- 为什么我们要学习Haskell这样的编程语言 (阅读:2216)
- 从元编程到信息编程的遐想 (阅读:1749)
- 从Java和JavaScript来学习Haskell和Groovy(引子) (阅读:1752)
- 从Java和JavaScript来学习Haskell和Groovy(类型系统) (阅读:1272)
- 从Java和JavaScript来学习Haskell和Groovy(DSL) (阅读:958)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:四火 来源: 四火的唠叨
- 标签: Groovy Haskell 元编程
- 发布时间:2016-03-07 23:57:54
- [66] Go Reflect 性能
- [66] Oracle MTS模式下 进程地址与会话信
- [65] 如何拿下简短的域名
- [59] IOS安全–浅谈关于IOS加固的几种方法
- [59] android 开发入门
- [59] 图书馆的世界纪录
- [58] 【社会化设计】自我(self)部分――欢迎区
- [53] 视觉调整-设计师 vs. 逻辑
- [47] 界面设计速成
- [47] 读书笔记-壹百度:百度十年千倍的29条法则