论面向组合子程序设计方法 之八 monad

仍然是先用oo把轮廓划出来,我们需要建模一个接口来围绕它进行组合。

因为本文是关于co的论述,那么这个接口怎样分析出来的就暂时忽略掉了:

java代码:

interface Dependency{ 
  Object getArgument(int i, Class type); 
  Class verifyArgument(int i, Class type); 
  Object getProperty(Object key, Class type); 
  Class verifyProperty(Object key, Class type); 
}

这个Dependency接口由每个不同的组件调用,来解决依赖。如果解析失败,则抛出异常。此处,我们暂时忽略异常这个细节。

getArgument负责解析一个函数参数,组件告诉Dependency对象,我需要给第3个参数,类型为String的解析依赖。于是就调用 getArgument(2, String.class)。

getProperty负责解析一个用某个key来标识的属性。比如一个javabean的property。

那两个verify是只取得解析到的那个符合要求的组件类型,但是并不实际创建对象。

然后是Component接口。这里,为了名字简短,我们不用ComponentAdapter这么恶长的名字,直接就是Component好了。

java代码:

interface Component{ 
  Class getType(); 
  Object create(Dependency dep); 
  Class verify(Dependency dep); 
}

getType()用来返回这个Component生成的对象的类型。 create用来创建这个对象。 verify用来保证这个对象可以被创建。

至于容器接口,再简单不过了。我们都知道pico不过是个hash table,yan的容器也差不多,虽然多几个getComponentOfType()的方法,但是大体上就是一个hash table。

java代码:

interface Container{ 
  Component getComponent(Object key); 
  Component getComponentOfType(Class type); 
} 

好了。oo完毕。下面来co。

首先,最简单的Component是什么?什么也不干,直接返回一个值。 java代码:

class ValueComponent implements Component{ 
  private final Object v; 
  public Class getType(){ 
    return v==null?null:v.getClass(); 
  } 
  public Object create(Dependency dep){ 
    return v; 
  } 
  public Class verify(Dependency dep){ 
    return getType(); 
  } 
} 

稍微难啃点的,是构造函数和工厂方法。这两个都会调用Dependency的getArgument()来取得自己需要的参数实例。 实际上,java的reflection api里面的Method和Constructor还是有很多相似点的。 为了抽取共性,我们定义一个新的接口,叫做Function:

java代码:

interface Function{ 
  Class getReturnType(); 
  Class[] getParameterTypes(); 
  Object call(Object[] args); 
}

这里,我就不展现把Method和Constructor匹配为Function的代码了,因为应该一目了然。 我们只要知道我们现在可以有三个函数产生Function对象:

java代码:

class Functions{ 
  static Function ctor(Constructor ctor); 
  static Function method(Object obj, Method mtd); 
  static Function static_method(Class type, Method mtd); 
}

当然,还有一些辅助函数, 比如: java代码:

static Function ctor(Class type);

然后是FunctionComponent。 java代码:

class FunctionComponent implements Component{ 
  private final Function f; 
  public Class getType(){ 
    return f.getReturnType(); 
  } 
  public Object create(Dependency dep){ 
    final Class[] types = f.getParameterTypes(); 
    final Object[] args = new Object[types.length]; 
    foreach(t:types){ 
      args[i] = dep.getArgument(i, t); 
    } 
    return f.call(args); 
  } 
  public Class verify(Dependency dep){ 
    final Class[] types = f.getParameterTypes(); 
    foreach(t:types){ 
      Class arg_type = dep.verifyArgument(i, t); 
      checkTypeMatch(types[i], arg_type); 
    } 
    return f.getReturnType(); 
  } 
} 

然后一个基本的component应该是java bean的setter了,对应pico的SetterInjectionComponentAdapter,也对应spring的bean。 java代码:

class BeanComponent  implements Component{ 
  private final Class type; 
  public Class getType(){ 
    return type; 
  } 
  public Object create(Dependency dep){ 
    Object r = createInstance(); 
    setJavaBeans(r,dep); 
  } 
  public Class verify(Dependency dep){ 
    ... 
  } 
}

具体的实现我省略了很多。因为会调用java.beans的api,并且会有一些caching优化的考虑,但是思路上很清楚,就是对每个property调用getProperty()就是了。

好,最基本的就这么几个了(其实,bean component并不是最基本的,后面我们会看到)。

下面看看都有些什么组合规则。

1。手工指定某个参数。 java代码:

class WithArgument implements Component{ 
  private final Component parent; 
  private final int pos; 
  private final Component arg; 
  public Class getType(){ 
    return parent.getType(); 
  } 
  public Object create(Dependency dep){ 
    return parent.create(withArg(dep)); 
  } 
  public Class verify(Dependency dep){ 
    return parent.verify(withArg(dep)); 
  } 
  private Dependency withArg(final Dependency dep){ 
    return new Dependency(){ 
      public Object getArgument(int i, Class type){ 
        if(i==pos){ 
          checkTypeMatch(type, arg); 
          return arg.create(dep); 
        } 
        else return dep.getArgument(i, type); 
      } 
    } 
    ... 
  } 
} 

好,通过decorate这个Dependency对象,我们得到了手工制定某个参数的能力。 这里,我们对参数仍然用Component,而不是一个简单的Object作为这个参数的值,是因为参数本身也可能需要创建,它的依赖关系也可能需要在Dependency对象中解析。如果参数不需要创建,那么,你尽可以用ValueComponent来包装一下。

2。手工指定property的值。跟上面的代码非常类似,就是重载了getProperty()和verifyProperty()。 java代码:

class WithProperty implements Component{ 
  private final Component parent; 
  private final Object key; 
  private final Component prop; 
  public Class getType(){ 
    return parent.getType(); 
  } 
  public Object create(Dependency dep){ 
    return parent.create(withProp(dep)); 
  } 
  public Class verify(Dependency dep){ 
    return parent.verify(withProp(dep)); 
  } 
  private Dependency withProp(final Dependency dep){ 
    return new Dependency(){ 
      public Object getProperty(Object k, Class type){ 
        if(k.equals(key)){ 
          checkTypeMatch(type, prop); 
          return prop.create(dep); 
        } 
        else return dep.getProperty(k, type); 
      } 
    } 
    ... 
  } 
} 

3。和很多组合子一样,map是一个相当有用的组合规则。它负责把一个Component返回的对象作一下额外的处理,transform成另外一个对象。 java代码:

interface Map{ 
  Object map(Object obj); 
}

java代码:

class MapComponent implements Component{ 
  private final Component c; 
  private final Map map; 
  public Class getType(){ 
    return null; 
  } 
  public Object create(Dependency dep){ 
    return map.map(c.create(dep)); 
  } 
  public Class verify(Dependency dep){ 
    c.verify(dep); 
    return Object.class; 
  } 
    ... 
} 

注意,这里,因为我们无法预先知道Map这个接口返回的对象会是什么类型,所以,我们让getType()返回null来标示这是一个动态决定的组件类型。

4。比map更一般化一点的,是bind动作。所谓bind,也是根据一个Component创建的对象来决定接下来返回什么动作。不同的是,它用这个对象来产生另外一个Component,让这个Component来生成一个新对象。多说无益,让我们看代码: java代码:

interface Binder{ 
  Component bind(Object obj); 
}

java代码:

class BoundComponent implements Component{ 
  private final Component c; 
  private final Binder binder; 
  public Class getType(){ 
    return null; 
  } 
  public Object create(Dependency dep){ 
    return binder.bind(c.create(dep)).create(dep); 
  } 
  public Class verify(Dependency dep){ 
    c.verify(dep); 
    return Object.class; 
  } 
    ... 
}

这个Binder接口看似简单,但是它的存在对整个co都是生死攸关的大事。可以说,如果没有这个Binder, co就基本可以不存在了。 为什么这么说呢?因为这个binder再加上前面的那个ValueComponent代表了一种非常一般性的计算模型:monad。有一个专门的数学分支:组论,就是研究monad的。 它虽然不是放之四海皆准的计算模型,比如,有比它更为一般性的Arrow模型。但是,用它几乎可以描述我们一般所遇到的大量问题。

除了前面的几个基本组合子之外,几乎所有的组合子,如果我们愿意,都可以从这个bind推衍出来。比如上面的map,如果用简洁点的函数式语法来表述的话(原谅我还是忍不住用函数式,java的语法就象一砣一砣屎一样压得我喘不过气来) java代码:

map(a, f) = bind (a, \x->value(f(x)));

这个代码的意思是说,你可以很轻易地把一个Map对象adapt到Binder对象,只要在bind函数里面调用: java代码:

return new ValueComponent(map.map(v));

就行了。

后面的很多组合子,比如对一个组件生成的对象调用某个方法,设置一些java bean setter,都是从这个bind组合子衍生出来的。

好了,今天时间紧迫,到此告一段落吧。