上次发表了VS2008亮点:用Lambda表达式进行函数式编程这篇文章后,有些人提出希望看C#版。其实我本来想等大家多尝试下能否自己实现的,可惜没有太多人实际思考这个问题,是不是觉得函数式编程离我们的日常生活太远……不管怎么说,这次我将公布强类型语言C#实现不动点组合子Y的方法,以及类型推导的全过程。不喜欢强类型思考的朋友看本文一定要做好头晕的准备……
首先复习一下不动点组合子,它就是这样的东西:(现在必须用VB语法,否则无法写出,见谅)
sig Math.Sin == <double, double>
这就是我们后面要用的签名分析法表达式。这里特别使用双等号,为了和表示数值相等的普通等号区分开。首先我们看一个Y组合子应用的例子,还是那个求阶乘的函数:
我们知道fact的签名和self的签名是一样的,因为它就是这样表示递归的。fact是计算阶乘的函数,所以有:
sig fact == <int, int>
sig Function(self) Function(n) If (...) == <<int, int>, <int, int>>
我们将问题一般化,认为Y的返回类型是一般性的<a, r>,其中a,r是任意类型,可以相同。因此我们就弄清楚了Y的参数类型与返回值类型:
sig Y == <YA, YR>
YA == <<a, r>, <a, r>>
YR == <a, r>
sig Y == <<<a, r>, <a, r>>, <a, r>>
最后一个Y的签名表达式就是Y的最终类型,为了便于理解我们引入YA, YR分别表示Y的参数类型和返回值类型。回到Y的定义,很明显我们知道f的签名就是Y的参数类型,而x的类型则是a,但h的类型非常不好判断。所以下面我们来推导h的类型。首先我们定义:
sig g(g) == YR == <a, r>
我们用gA, gR分别表示g的参数类型和返回值类型,则有
<gA, gR>(<gA, gR>) == <a, r>
观察这个签名表达式,可以得出
gA == <gA, gR> == <gA, <a, r>>
gR == <a, r>
我们看到gA存在递归定义!这就是Y实现递归的体现。gA必须具有<gA, <a, r>>的形式,也就是说gA应该能重新解释为包含自己的另一个类型。写出不包含gA的gA定义是不可能的,因此我们换用弱类型的思路。我们将gA定义为<?, <a, r>>,其中?我们不指定具体的类型,而到用的时候我们把它重新解释为<?, <a, r>>类型。很显然,在C#中object类型可以担此重任。现在我们清楚了,g的签名是这样的:
sig g == <<object, <a, r>>, <a, r>>
结合g的定义,我们现在就可以得出h,x的类型或签名了:
sig h == <object, <a, r>>
sig x == a
现在类型分析结束,我们尝试编写第一个C#版,为了简单起见,a, r这两个类型我们也全都用object来填充:
这个实现采用的是object来代替具体a, r的弱类型实现,如果想实现指定a, r具体类型的Y实现,需要将Y定义在泛型方法中。有兴趣(而且还没晕)的朋友可以继续尝试啦,呵呵。
首先复习一下不动点组合子,它就是这样的东西:(现在必须用VB语法,否则无法写出,见谅)
Dim Y = Function(f) _
(Function(h) Function(x) f(h(h))(x)) _
(Function(h) Function(x) f(h(h))(x))
对任意函数f,Y(f)就等价于f(Y(f)),具体可见前一篇文章。诸位可见,如果强类型地思考,Y的参数f的类型并不是任意的,它必须也是一个函数,能够接受Y(f)作为参数。下面需要很多类型推导的步骤才能将f的类型推出,所以首先引入一个形式化推导类型用的“签名分析法”。这种分析方法是从强类型函数式语言Standard ML中获得灵感的,在那种语言中函数签名是一种实际存在的语法结构。我们用sig f这种记法来表示函数f的签名。如果f不是函数(而是单纯变量),则sig f表示f的数据类型。我们还用<TA, TR>这种形式表示一般的一元函数,其参数类型为TA,返回值类型为TR。比方说Math.Sin函数接受double作为参数,返回类型也是double,我们就有:(Function(h) Function(x) f(h(h))(x)) _
(Function(h) Function(x) f(h(h))(x))
sig Math.Sin == <double, double>
这就是我们后面要用的签名分析法表达式。这里特别使用双等号,为了和表示数值相等的普通等号区分开。首先我们看一个Y组合子应用的例子,还是那个求阶乘的函数:
Dim fact = Y(Function(self) Function(n) If(n = 0, 1, n * self(n - 1)))
我们知道fact的签名和self的签名是一样的,因为它就是这样表示递归的。fact是计算阶乘的函数,所以有:
sig fact == <int, int>
sig Function(self) Function(n) If (...) == <<int, int>, <int, int>>
我们将问题一般化,认为Y的返回类型是一般性的<a, r>,其中a,r是任意类型,可以相同。因此我们就弄清楚了Y的参数类型与返回值类型:
sig Y == <YA, YR>
YA == <<a, r>, <a, r>>
YR == <a, r>
sig Y == <<<a, r>, <a, r>>, <a, r>>
最后一个Y的签名表达式就是Y的最终类型,为了便于理解我们引入YA, YR分别表示Y的参数类型和返回值类型。回到Y的定义,很明显我们知道f的签名就是Y的参数类型,而x的类型则是a,但h的类型非常不好判断。所以下面我们来推导h的类型。首先我们定义:
g = Function(h) Function(x) f(h(h))(x))
也就是说,为Y中调用自身的函数起一个名字,很显然:sig g(g) == YR == <a, r>
我们用gA, gR分别表示g的参数类型和返回值类型,则有
<gA, gR>(<gA, gR>) == <a, r>
观察这个签名表达式,可以得出
gA == <gA, gR> == <gA, <a, r>>
gR == <a, r>
我们看到gA存在递归定义!这就是Y实现递归的体现。gA必须具有<gA, <a, r>>的形式,也就是说gA应该能重新解释为包含自己的另一个类型。写出不包含gA的gA定义是不可能的,因此我们换用弱类型的思路。我们将gA定义为<?, <a, r>>,其中?我们不指定具体的类型,而到用的时候我们把它重新解释为<?, <a, r>>类型。很显然,在C#中object类型可以担此重任。现在我们清楚了,g的签名是这样的:
sig g == <<object, <a, r>>, <a, r>>
结合g的定义,我们现在就可以得出h,x的类型或签名了:
sig h == <object, <a, r>>
sig x == a
现在类型分析结束,我们尝试编写第一个C#版,为了简单起见,a, r这两个类型我们也全都用object来填充:
Func<Func<Func<object, object>, Func<object, object>>, Func<object, object>> Y = f =>
{
Func<Func<object, Func<object, object>>, Func<object, object>> g = h => x => f(h(h))(x);
return g(g);
};
很有成就感吧,但是这个却不能执行!因为我们没有完成刚才gA递归解释的步骤,所以g不能作为g的参数直接传递。为什么呢,我们看看刚才g的签名分析,g自身的类型是<<object, <a, r>>, <a, r>>,而g的参数则是<object, <a, r>>型,我们需要把<object, <a, r>>重新解释为object类型才能工作,这就应了刚才gA分析中递归调用的结论。我们需要编写一个方法来进行这项变换:{
Func<Func<object, Func<object, object>>, Func<object, object>> g = h => x => f(h(h))(x);
return g(g);
};
static Func<object, Func<T2, T3>> Untype<T1, T2, T3>(Func<T1, Func<T2, T3>> src)
{
return o => o2 => src((T1)o)(o2);
}
光是这个Untype的定义,所用的Lambda表达式就已经超出相当多人写过Lambda表达式的难度了把。此处用到的技巧在编写很多函数式程序时都可能会用到。现在,我们得出了最终版的Y:{
return o => o2 => src((T1)o)(o2);
}
Func<Func<Func<object, object>, Func<object, object>>, Func<object, object>> Y = f =>
{
Func<Func<object, Func<object, object>>, Func<object, object>> g = h => x => f(h(h))(x);
return g(Untype(g));
};
注意其中Untype所发挥的作用。最后我们也用C#的Y来实现匿名的递归阶乘函数,来验证一下我们刚刚复杂的分析是否正确:{
Func<Func<object, Func<object, object>>, Func<object, object>> g = h => x => f(h(h))(x);
return g(Untype(g));
};
Func<Func<object, object>, Func<object, object>> my_f = g => n => (int)n == 0 ? 1 : (int)n * (int)g((int)n - 1);
Func<object, object> fact = Y(my_f);
int result = (int)fact(8);
Func<object, object> fact = Y(my_f);
int result = (int)fact(8);
这个实现采用的是object来代替具体a, r的弱类型实现,如果想实现指定a, r具体类型的Y实现,需要将Y定义在泛型方法中。有兴趣(而且还没晕)的朋友可以继续尝试啦,呵呵。