如何写递归的 Lambda 表达式

这个问题貌似很简单,不是么?随便写一个:

Func<int, int> factorial = x => x == 0 ? 1 : x * factorial(x - 1);

但是这个通过不了编译,因为你在用 factorial 定义 factorial,编译器会提示 factorial 是未被赋值的变量:

错误提示

不少人提出,以下这样不就行了么?

Func<int, int> factorial = null;
factorial = x => x == 0 ? 1 : x * factorial(x - 1);

聪明,问题貌似解决了!不过前提是你不改变 factorial,考虑继续写以下代码:

var f2 = factorial;
factorial = i => i;
Console.WriteLine(f2(5));

不妨猜猜输出结果?恭喜你,是 20。原因很简单,因为 factorial = i => i,所以把 f2 展开后就变成了:

x => x == 0 ? 1 : x * (i => i)(x - 1)

拜闭包所赐,这就不是我们想要的 factorial 函数啦!这方法在很多时候可以用,但要避免改变 factorial。现在我们来找一种没有这么容易出错的方法。

 

函数映射函数(传说中的高阶函数?)

回到第一个式子,我们遭到了未知量。通常呢,我们不知道一样东西的时候,我们把它设成未知数,然后方程照列,按编程的说法呢,就是把它提成参数,再想办法让其他人搞好了传进来用(传说中的 IoC ?):

Func<Func<int, int>, int, int> factorial = ( fac, x ) => x == 0 ? 1 : x * fac(x - 1);

看上去挺不错的,但是 FP 惯的朋友会认为这样更好:

Func<Func<int, int>, Func<int, int>> map = fac => x => x == 0 ? 1 : x * fac(x - 1);

这样,函数两个参数之间的关系不再是平行的,而是变成了一个函数,它取一个函数作为参数,返回另一个函数,这另一个函数取一个数字,并抓取第一个函数的接收的参数(闭包)做一点了东西。换一句话说,map 这个函数接收的参数和返回的值的类型都是函数,也就是传说中的高阶函数,以我的理解来说就是将一个函数映射到了另一个函数(希望这个叙述还说得过去吧)。

这个主意非常疯狂的说,现在我们只需让“人家”告诉我们自己想要什么,我们拿过来 map 一下,就得到想要的东西了。显然,现在的问题是,这个“人家”,其实就是我们。

 

这个“人家”就是我们(传说中函数的固定点?)

高中数学:如果一个函数 f(x) 存在 x = f(x) 这些点,那么这些点就叫做函数 f(x) 的固定点。当然,定义域和值域都是实数。但是如果定义域和值域都是函数的话呢?

来看上面的 map 函数,假设我们找到了它的固定点 Factorial,扔给 map 函数,展开:

map(Factorial) =
x => x == 0 ? 1 : x * Factorial(x - 1)

根据固定点的定义,map(Factorial) = Factorial,代入上式,即有

Factorial =
x => x == 0 ? 1 : x * Factorial(x - 1)

这不就是我们一开始写的那条式子么?所以结论就是,如果人家(我们)找到 map 函数的固定点,那么我们拿过来,扔给 map 函数,那么它返回的函数就是我们要的函数!

下面我们来找 map 函数的固定点。在实数域寻找这些点可以变得很困难(虚心向各位请教怎么求 f(x) = (1/16)x 的固定点,不是近似啊,感激不尽),但寻找这种定义域和值域都是函数的函数,却有伟人肩膀让我们踩。如下图是一个很娱乐的 Fixed point combinator:

For amusement

有兴趣可以试试,但这里我们用一种实用的方法来找,那就是写一个方法,接收这个 map 函数,并返回它的固定点:

static Func<T, T> Fix<T>( Func<Func<T, T>, Func<T, T>> map )
{
    // ...
}

那么,其实 Fix(map) 就是我们要找的固定点了:

static Func<T, T> Fix<T>( Func<Func<T, T>, Func<T, T>> map )
{
    return Fix(map);
}

有没有搞错?!这样我们简直像是在胡闹,陷入了死循环!

记住,我们是在找固定点,现在却没有用上固定点的定义!令 F = Fix(map),那么 F = map(F),也就是像在之前说的那样,map 一下:

static Func<T, T> Fix<T>( Func<Func<T, T>, Func<T, T>> map )
{
    return map(Fix(map));
}

这样,我们就将 map 的固定点,也就是目标函数,递归地展开了。So far, so good, isn’t it?

其实我们这样做只是递归地展开一个函数的固定点,也就是它本身,这个过程是无限的,这等于在说,现在有人需要一个函数的固定点,得先等无限长时间,我们才不要做这个倒霉的家伙呢!事实上,只要知道了具体输入值,而这个函数在这个输入值下能停止的话,这个展开就可以停止了(当然是这样了,感觉这句话废废的……),那么我们应该返回一个函数,它知道输入值后才去展开目标函数:

static Func<T, T> Fix<T>( Func<Func<T, T>, Func<T, T>> map )
{
    return x => map(Fix(map))(x);
}

这个技巧的描述让我想起了 jQuery Tutorials 的一段话……

 

总结

都说完了,那就上代码吧……

class Program
{
    static void Main( string[] args )
    {
        Func<Func<int, int>, Func<int, int>> map
            = fac => x => x == 0 ? 1 : x * fac(x - 1);

        var factorial = Fix(map);

        Console.WriteLine(factorial(5));
        Console.ReadKey();
    }

    static Func<T, T> Fix<T>( Func<Func<T, T>, Func<T, T>> map )
    {
        return x => map(Fix(map))(x);
    }
}

对 factorial 的定义也可以写成:

var factorial = map(Fix(map));

不过没必要了……

posted @ 2009-08-15 23:05 DiryBoy 阅读(...) 评论(...) 编辑 收藏