zoukankan      html  css  js  c++  java
  • 如何写递归的 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));
    

    不过没必要了……

    - DiryBoy
    http://DiryBoy.cnblogs.com
  • 相关阅读:
    打印九九乘法表
    PAT (Basic Level) Practice (中文) 1091 N-自守数 (15分)
    PAT (Basic Level) Practice (中文)1090 危险品装箱 (25分) (单身狗进阶版 使用map+ vector+数组标记)
    PAT (Basic Level) Practice (中文) 1088 三人行 (20分)
    PAT (Basic Level) Practice (中文) 1087 有多少不同的值 (20分)
    PAT (Basic Level) Practice (中文)1086 就不告诉你 (15分)
    PAT (Basic Level) Practice (中文) 1085 PAT单位排行 (25分) (map搜索+set排序+并列进行排行)
    PAT (Basic Level) Practice (中文) 1083 是否存在相等的差 (20分)
    PAT (Basic Level) Practice (中文) 1082 射击比赛 (20分)
    PAT (Basic Level) Practice (中文) 1081 检查密码 (15分)
  • 原文地址:https://www.cnblogs.com/Diryboy/p/HowToWriteRecursiveLambdaExpression.html
Copyright © 2011-2022 走看看