Functor组合, 参数集合, Curry

Ajoo曾经写过面向组合子编程系列。我也帮着助威。
面向组合子编程和并不是简单意义上的Composite Pattern。Composite Pattern只是一个简单的基本Pattern。
面向组合子编程只是用到了Composite Pattern,面向组合子编程本身的内容复杂许多,以至于复杂到这样的程度,数据和行为必须分开,形成Visitor Pattern。
而一般意义上的Compositor Pattern都是数据和行为在一起的对象的组合。
用于面向组合子编程中,一般是指只有行为没有数据的Functor对象进行组合。

基本的模型是这样。包括3个参与部分。算法,Functor算子,参数。
算法基本上是固定的,Functor算子可能自由组合,参数也可能一个或者多个。

下面根据Functor和参数这两个方面介绍面向组合子编程的3种类型。

1. Functor组合 – Combinator, Pipe
Functor有多个,参数只有一个。
这一类的特点是,Functor移动,参数不动。

这是最常见的一类。
常见于用于工作流程控制的逻辑组合子。for each, or, and, not, if, else 等。
Hibernate的 Criteria Query 的 And , Or , Like, Equals的组合,也属于这一类。

[code:1]
ForEachCombinator( param ){ // And, Or
for( functor in functors ){
… functor( param); …
// do some thing.
// 比如, or 的时候,第一个符合条件,就返回
// and 的时候,第一个不符合条件,就返回
}
}
[/code:1]
ForEachCombinator可以扩展为 OrCombinator,AndCombinator。

比较有趣的是,Pipe这种类型。
Pipe也是多个Functor,一个参数。但和上述的For each 之类的Combinator不同的是,下一个Functor的参数,是上一个Functor的返回结果。

[code:1]
Pipe( param ){
for( functor in functors ){
param = functor( param);
// do some thing.
}
}
[/code:1]

我们给Pipe模式加上一些逻辑判断,和其他的Combinator组合,就可以用来实现if, then之类复杂一些的条件组合逻辑。
比如,我们给Pipe加上一个中断条件。
[code:1]
Pipe( param ){
for( functor in functors ){
param = functor( param);
if(param is not valid) // null, false, etc
break;
}
}
[/code:1]

pipe = new Pipe( {new Equls(1), new Printer });
表示参数是1的时候,会执行printer。
pipe(1) 就可以打。
pipe(2) 就不打印。

这里只是一个简单示意。比较详细的例子和实现,可以看ajoo的blog和帖子。

2. 参数集合 – Visitor, Map, Filter, Reduce
这一类比较有趣。参数是多个,是一个集合,Functor可以是一个(也可能是多个)。
这一类的特点是,Functor不动,参数移动。

FP的Map, Filter算法都是这样,接受一个集合参数(list, stream),都经过同一个Functor的处理,当然这个Functor本身可能是组合过的。
Reduce算法也是这样,只是有点不同。Reduce多了一个Continuation, Context作用的环境变量参数,这个参数上一次处理的结果,传递给同一个Functor的下一次调用。
自己产生参数,自己消费,这个行为模式,和Pipe有点类似。但是不同。
Reduce算法反复调用一个Functor,并遍历参数集合。Pipe调用多个Functor,而且每个Functor只调用一次。

3. Functor组合,参数集合 – Curry, Factory Chain
最复杂的类型,就是这一类了。Functor移动,参数也移动。

关于Curry的概念和例子,本文不展开。网上有不少好文章。可以搜索到。
Curry 很类似于 Factory Chain。
举个例子。F(x, y, z) = 2 * x + y - z。
F(1, 2, 3)
用Curry的方式写就是,f(1)(2)(3)。这是FP语法。
换成类似于c, java, c#的语法来写,就是Factory Chain的写法。
factory.generate(1).generate(2).generate(3);

这里的关键是,上一个Functor处理参数的结果产生了下一个Functor。
下一个Functor继续处理下一个参数,产生下一个Functor。

BigDecimal.add(…).add(…).multiply(…).substract(…)
StringBuffer.append(…).append(…)
factory.newSession(..).newQuery(…).setParameter(…).setParameter(…)

等等形式,都具有Curry, Factory Chain的形式特点。虽然有时候,Functor返回的是自己。

Design Pattern的 Abstract Factory ( Factory of Factor ) 可以扩展成 Factory of Factor of Factory .., 就成了Factory Chain。也具有Curry, Factory Chain的形式特点。

Curry, Factory Chain在问题降维(减少参数个数)方面有很大用处。
评论
buaawhl 2006-08-11
yimlin 写道
我觉的三种类型都对functor提出了不同的要求

1. 对于第一种类型可以借用DDD的specification的and和or以及not操作

[code:1]
functor = functor1.and(functor2).or(functor3).and(functor4)

ForEachCombinator( param ){
functor(param);
....
}
[/code:1]

这样充分利用functor的闭包的能力。

Pipe对functor的要求,就像BigDecimal一样,functor成为操作符,操作值和返回值是同一类型。
而非Pipe的functor就不需如此,比如刚刚的示例代码:functor接受一个param但返回值不一定是该类型,这样就只要求所有的functor的返回值是同一类型就可以支持闭包。


对于第三种情况,通常基于的假设是functor本身是constant,在hibernate的设计中充分体现了这点!

三种类型对于functor的要求不一样,在OO世界里还真要费一番周折才能像FP一样! :(


我觉得,你举的例子都是第三种curry。
functor成为操作符,操作值和返回值是同一类型,体现的就是第三种curry。
因为这种链式调用的作用,就是减少参数个数。
yimlin 2006-08-10
我觉的三种类型都对functor提出了不同的要求

1. 对于第一种类型可以借用DDD的specification的and和or以及not操作

[code:1]
functor = functor1.and(functor2).or(functor3).and(functor4)

ForEachCombinator( param ){
functor(param);
....
}
[/code:1]

这样充分利用functor的闭包的能力。

Pipe对functor的要求,就像BigDecimal一样,functor成为操作符,操作值和返回值是同一类型。
而非Pipe的functor就不需如此,比如刚刚的示例代码:functor接受一个param但返回值不一定是该类型,这样就只要求所有的functor的返回值是同一类型就可以支持闭包。


对于第三种情况,通常基于的假设是functor本身是constant,在hibernate的设计中充分体现了这点!

三种类型对于functor的要求不一样,在OO世界里还真要费一番周折才能像FP一样!
buaawhl 2006-07-27
Pipe的一个例子。
我们来看一个转换conversion的需求。
假设我们已经有,a -> b; b -> c; a -> c; b -> a; c -> a; 等需求。
有两种设计方式。

第一种,一个method的converter interface。
Interface Converter {
String convert(String source);
}

假设,我们已经有了最基本的
converterAB, conveterBA, converterBC, convertCB。
那么,我们可以用Pipe来简单组合converterAC, converterCA。

converterAC = new Pipe(converterAB, converterBC);
converterCA = new Pipe(converterCB, converterBA);


第二种,两个pair methods的bi direction converter interface。
interface BiDirectionConverter {
String convertOneWay(String s);
String convertAnotherWay(String s);
}

假设,我们有了converterAB, converterBC等基本类。

converterAC可以这么组装

[code:1]
class ConverterAC implements Converter{
String convertOneWay(String s){
b = converterAB.convertOneWay(s);
c = converterBC.convertOneWay(b);
return c;
}

String convertAnotherWay(String s){
b = converterBC.convertAnotherWay(s);
a = converterAB.convertAnotherWay(b);
return a;
}
}
[/code:1]

假设总共有 N 种需要转换的对象。
那么,第一种方案,需要的类个数是 N * 2 个。线性增长。
第二种方案,需要的类个数是,2 的 (N – 1)次方 - 1。2 ^ (N- 1) -1。指数增长。
当N个数很大的时候,第2种方案的类型膨胀问题十分明显。

我们看到,
b = converterAB.convertOneWay(s);
c = converterBC.convertOneWay(b);
都调用的是,covertOneWay,

b = converterBC.convertAnotherWay(s);
a = converterAB.convertAnotherWay(b);
都调用的是,convertAnotherWay,

我们也可以做两个Pipe。
OneWayPipe
AnotherWayPipe

还需要做一个Proxy converter
[code:1]
Class ProxyConverter implements Converter{
Converter oneWay, anotherWay;

String convertOneWay(String s){
return oneWay.convertOneWay(s);
}

String convertAnotherWay(String s){
return anotherWay.convertAnotherWay(s);
}
}
[/code:1]

然后做一个 converter pipe factory

[code:1]
createPipe (a1, a2){
oneWay = new OneWayPipe(a1, a2);
antherWay = new anotherWayPipe(a2, a1);

return new ProxyConverter(oneWay, anotherWay);
}
[/code:1]

这样也可以解决类型膨胀的问题,只是麻烦了一些。

---------

上述说明:interface的宽度(method个数),是可扩展性的天敌。
如果可能,尽量减少interface的宽度(method的个数)。

一个class的不同method,有相同的需求怎么办?
比如,log, cache, 权限控制。

这时候为了重用,只能用AOP了。
如果是只有一个method,那么最理想,OO就可以了。
buaawhl
搜索本博客
其他分类
存档
最新评论