Posted in December 2009

实现DSL的几个例子

所谓DSL就是能描述语言的语言,一般某些领域都有自己的专有术语,用这些术语来交流比一般的通用的语言更流畅,比如象棋的“平五进八”,这种走棋的表达比自然语言更快,DSL就是把这种领域的语言翻译成“自然语言”的模型。一般有内部和外部之分,内部指领域语言嵌入在自然语言中用自然语言表达,相当于从“古文”中进化出“普通话”的过程;外部则指全新的语言,比如Java, C等,是一个全新的描述模型,创造起来比内部要复杂。举几个例子容易明白,下面几个是内部的DSL。

  • 使用连贯接口
  • 这是”The productive Programmer”中的例子,要描述的领域是“食谱”。比如“recipe.add 200.grams.of Flour”。

    class Numeric
      def gram
       self
      end
    
      alias_method :grams, :gram
    end

    Ruby可以打开一个存在的类向其中添加方法,所以可以写出200.grams这种有点恶心的句子,而且ruby中方法可以不加括号(),光这点用Java实现就够呛。不过用method chain会使用很多点点点,有点像人没有进化完全而留下点阑尾…

    这种open class的做法在其他语言中也可以遇到。Javascript中是直接修改类的prototype,Objective-C中也可以,比如扩展NSString使其可以转化为Json格式。但是Open class也有问题,因为Open class直接修改了原来的类,这会蔓延到整个类型系统,违反了模块化和封装的原则,所以这个修改最后只在某个范围内有效。

  • 下面这个例子来自”Programming Scala Tackle Multi-Core Complexity on the Java Virtual Machine”,要描述的领域为“时间”,比如“2 days ago”。
  • object App
    {
      def main(args : Array[String]) : Unit =
      {
        implicit def convertInt2DateHelper(number: Int) = new DateHelper(number)
    	val ago = "ago"
    	val from_now = "from_now"
    	val past = 2 days ago
    	val appointment = 5 days from_now
    	println(past)
    	println(appointment)
      }
    
    	import java.util._
    	class DateHelper(number: Int) {
    		def days(when: String) : Date = {
    			var date = Calendar.getInstance()
    			when match {
    				case "ago" => date.add(Calendar.DAY_OF_MONTH, -number)
    				case "from_now" => date.add(Calendar.DAY_OF_MONTH, number)
    				case _ => date
    			}
    			date.getTime()
    		}
      	}
    }

    这里面当Scala遇到Int类型的2时,会自动的选择implicit的方法把这个整数转换为DateHelper类,不清楚这是语言的功能还是VM的特征,这里并没有扩展Int类型:加上days的方法。Scala有个Traits的功能,倒是可以扩展类,暂时不大清楚。

  • 计算器
  • 这里有讲到:面向 Java 开发人员的 Scala 指南: 构建计算器,第 1 部分 解决如何计算如((5 * 10) + 7)这样的表达式。这倒是一个DSL的好例子,不过文章最后把核心问题交给了Becker-Naur Form(BNF),这就有点走向外部DSL的,这么简单的表达式也很难搞,看来内部DSL不能描述复杂的语法结构,高级一点的又得求助于语法解析器。

    在观察者模式中使用弱引用

    观察者模式一般都用inner class来实现,比如Android中如下的类:

    public abstract class AbstractCursor {
        public void setNotificationUri(ContentResolver cr, Uri notifyUri) {
                mSelfObserver = new SelfContentObserver(this);
            }
        }
    
         /**
         * Cursors use this class to track changes others make to their URI.
         */
        protected static class SelfContentObserver extends ContentObserver {
            WeakReference mCursor;
    
            public SelfContentObserver(AbstractCursor cursor) {
                super(null);
                mCursor = new WeakReference(cursor);
            }
    
            @Override
            public boolean deliverSelfNotifications() {
                return false;
            }
    
            @Override
            public void onChange(boolean selfChange) {
                AbstractCursor cursor = mCursor.get();
                if (cursor != null) {
                    cursor.onChange(false);
                }
            }
        }
    }

    这里使用了很少见的WeakReference(弱引用):

    weak-ref

    因为在一般的Observer模式中,对于observer本身可能因为忘记unregister(或者在异常情况下没有unregister),这会导致内存的泄漏。这里在AbstractCursor和observer之间建立了弱引用,让前者可以及时得到回收。而observer和event source还是强引用,可能因为observer还是很亲量级的原因吧。

    比较如下的另外一种实现,这种实现更加普遍:

    public abstract class CursorAdapter  {
        protected void init(Context context, Cursor c, boolean autoRequery) {
            mChangeObserver = new ChangeObserver();
        }
        private class ChangeObserver extends ContentObserver {
            public ChangeObserver() {
                super(new Handler());
            }
    
            @Override
            public boolean deliverSelfNotifications() {
                return true;
            }
    
            @Override
            public void onChange(boolean selfChange) {
                onContentChanged();
            }
        }

    如果你对内部类很熟悉的话(我是一点都不熟,重翻了遍thinking in java,经典啊),就会发现最前面的那种实现其实是nested class,后面一种才是inner class,由于inner class默认存在一个到“父类”的强引用,所以如果要用弱引用的话,必须把它改造成nested class(static class必为nested class),通过构造函数来传递“父类”的引用。

    另:我用Rational Team Concert的Find bugs,对没有使用内置强引用的inner class但是没有声明为static的,有这样的建议:

    Pattern: Should be a static inner class
    id: SIC_INNER_SHOULD_BE_STATIC, type: SIC, category: PERFORMANCE

    This class is an inner class, but does not use its embedded reference to the object which created it.  This reference makes the instances of the class larger, and may keep the reference to the creator object alive longer than necessary.  If possible, the class should be made static.

    弱引用孙卫琴老师的这篇对象的强、软、弱和虚引用讲的很透彻,虽然没有多高明多复杂的地方,但是简单明了,难得。

    如何编译x86平台的Dalvik

    Android Source中默认的Dalvik编译目标是ARM平台,只能在模拟机或者真机上运行,不过如果想研究它我觉得还是在x86下方便点。这里主要参考了android-porting下的这篇帖子,按照步骤来问题不大。(后来发现其来源于源代码目录下的docs/hello-world.html)

    0. 如果使用Ubunut Karmic的话,把gcc版本换成4.3的。

    sudo apt-get install gcc-4.3 g++-4.3
    sudo ln -s /usr/bin/gcc-4.3 /usr/bin/gcc
    sudo ln -s /usr/bin/g++-4.3 /usr/bin/g++

    默认的4.4 compile时要求更严格,会出现error: invalid conversion from
    ‘const char*’ to ‘char*’
    的提示。

    1. 在源代码跟目录下,运行:

    . build/envsetup.sh
    lunch 2

    这主要设置编译的目标平台。

    2. 编译相对应的模块:

    make dalvikvm core ext framework android.policy services

    dalvikvm后面的几个模块是VM本身需要的一些library,比如ext.jar。如果make所有模块也可以,不过耗时就长多了,而且出现编译错误的可能性就大多了,对于我这种只懂Java的人来说handle起来很困难… 另外,使用make modules可以得到一所有模块的列表。(使用repo sync project1 project2 …可以只check out某些特定的project)

    剩下的就是运行一个hello world的java类了,可以参考前面提到的帖子,相对容易。

    成功容易复制么?

    更多的时候,我们对于别人成功的过程只是片面的,一知半解的,道听途说的。包括成功的原因,成功的艰辛。很多时候,那只是一个传说,过于相信就等于迷信。

    我们对于从开始到成功的推理中缺少充分的证据链,常常认为某人成功就是因为他年轻时如何如何。以为我也只要如此这般就可以复制它的成功了,这是简单的模仿,容易“画虎不成反类犬”,更容易让自己踌躇不前。

    怎么办呢?抛弃被动的模仿,主动的探寻成功之路。这也要从生活的点点滴滴来锻炼。