IT技术博客大学习 共学习 共进步
全部 移动开发 后端 数据库 AI 算法 安全 DevOps 前端 设计 开发者

从Java和JavaScript来学习Haskell和Groovy(元编程)

四火的唠叨 2016-03-07 23:57:54 累计浏览 2,726 次
本机暂存

本篇文章的话题是元编程。首先来认识元编程,我在第一篇《引子》里面已经介绍:元编程,指的是在运行时改变“类”的定义,例如访问、增加或修改等等。一言以蔽之,就是“用程序来写程序”。在第二篇的《类型系统》里面已经借由继承和接口的实现,介绍了一些利用元编程特性来增加或改变子类行为的方法。回顾语言发展的长河,其实是经历了一个从“对象 -> 类 -> 元类”到“对象 -> 原型”的发展过程的。所以,无论是类,还是元类,这样的概念其实都不是非有不可的,只是因为我们思考的习惯,特别是抽象的习惯而顺其自然地产生了。这一点我在《编程范型:工具的选择》里面已经详细描述了,建议在往下阅读前移步。

正式介入元编程的部分,先来看看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.

所以,程序员啊,开心最重要了。(-_-)~…

同分类推荐文章

  1. 如何写好设计文档? (2026-06-23 08:00:00)
  2. Designing With Uncertainty: How AI Supercharges Probabilistic Thinking (2026-06-16 23:00:00)
  3. The Benefits Of Cognitive Inclusion In UX Research (2026-06-10 18:00:00)

查看更多 设计 文章 →

建议继续学习

  1. SmartSprites - 命令行形式的CSS Sprites生成器 (累计阅读 123,898)
  2. JQuery实现Excel表格呈现 (累计阅读 48,350)
  3. Java开发岗位面试题归类汇总 (累计阅读 22,159)
  4. android 开发入门 (累计阅读 19,531)
  5. 深入理解Javascript之执行上下文(Execution Context) (累计阅读 18,404)
  6. 从输入 URL 到页面加载完成的过程中都发生了什么事情? (累计阅读 15,934)
  7. 图片动态局部毛玻璃模糊效果的实现 (累计阅读 14,849)
  8. 天朝第二代身份证号码的验证机制 (累计阅读 14,763)
  9. HTML 5 的data-* 自定义属性 (累计阅读 14,349)
  10. 分享一个JQUERY颜色选择插件 (累计阅读 14,223)