美文网首页
懒惰模式

懒惰模式

作者: golden_age | 来源:发表于2016-07-24 17:17 被阅读0次

    <i>"懒惰、傲慢、缺乏耐性是程序员的三大美德"——Larry Wall(Perl's father)</i>

    很久以前,《设计模式》是本“红宝书“之类的读物,你要是面试时谈些"模式"总会有加分,同事有时也说,"哦,这里是个singleton模式, 那里是个clone模式"。今天,我们不怎么谈模式,遇到问题时,总结出一些套路,有时套路有用,有时套路却行不通,成为阻碍。

    我想,程序设计里“思而不学则die, 学而不思则money",是一个普遍问题,因此,我有点打算,介绍一些更为简单的”模式”,”简单“但能引起"本质”思考,希望这些”简单“的模式能够帮助你多思考。

    模式之——懒惰

    • c语言允许这样的语句
      my_assert = (true | (1/0)>millon);
      我们知道(1/0)根本不会发生,这被称为短路求值,

    • 三目运算符,类似
      gain = stupid_test ? million : (1/0);

    • 嫌 if 太啰嗦,三目又不好看,有人希望写一个漂亮的inline函数,

        int my_if(test, trueValue, falseValue)
        {
            if(test) trueValue else falseValue;
        }
        //usage
        gain = my_if(stupid_test, 1000000, (1/0) )
      

      然而,他发现这样根本不行!(1/0)总是被求值!

    • scala的解决方案,

      def my_if(p: boolean, true_value : =>int, false_value : => int) 
      gain = my_if(stupid_test, 1000000, (1/0) )
      //perfect
      

      解释:
      =>int是一个表示这里是函数类型,输入为空,返回为int,

    • c#的办法
      int my_if(bool p, func<int> trueValue, func<int> falseValue)
      {
      if(p) trueValue() else falseValue()
      }
      //丑了点,但将就
      my_if(true, ()=>million, ()=>1/0 )

    lazy模式说的是,按需求值,只有在必要时,计算才发生
    你可能已经发现,传入函数是实现lazy的关键

    上面是些无用的例子,说些实际的例子吧:

    • log 系统
      在c#, java一类代码里面, 我们常常有c语言里不存在的烦恼(因为没有宏),比如说,
      内循环里,做log是一件需要很小心的事,因为我们常常写成这种形式,
      if(log_level > LOG_DEBUG) log.e( string.format("state: %d, %d, %d", getx(), gety(), getz()) )

      在未打开调试开关时,这里只造成一个 if的开销,如果没有<i>if</i>, <i>string.format</i> 总会发生,造成有影响的开销,但写起来还是麻烦的。
      解决办法呢,almost no,一个比较丑陋,但有效的办法是:
      void lazy_log(debug_lv, Func<string> cont)
      {
      if(log_level > LOG_DEBUG) log.w(cont());
      }
      //usage,未开调试前,format不会发生
      lazy_log(log_level, ()=>string.format("state: %d, %d, %d", getx(), gety(), getz()) )
      (真正的办法还是需要DSL上的一些支持)

    • 单例,
      static T _ins = null;
      static T getInstance()
      {
      if(_ins == null) _ins = new T(); //第一次发生
      return _ins;
      }
      首次getInstance时,将实例初始化,这也是一个按需求值的例子,
      我们想象一个有很多module的系统,系统之间存在依赖性,比如说moduleA, 需要moduleB先启动,

    有时我们显式地去调用它.

      void system_ini()
      {
        ModuleA.init()
        ModuleB.init()
        ...
      }
    

    这样的话,我们需要显式维护初始化过程,但使用单例,我们只需要getInstance,让正确的初始化顺序自动发生!

    • 衍生出的 memory 模式
      在求值时,我们总在第一次求值,之后将之存于字典(而不先将之计算存于字典),这是一种空间与时间的均衡的技术,
      int hard_calc(int i)
      {
      if(_cache.find(i) ) return _cache.get(i);//
      int result;
      ////very hard word...
      _cache.add(i, result);
      return result;
      }
      如果你做过游戏物件管理,这种模式到处可见。

    • 懒惰的本质,是按需求值
      在大多数编程语言里,传入函数的参数,是一种严格求值,但并非是所有语言都是严格求值,但,还有一类语言,具有不同的求值策略,比如说 haskell,

      懒惰,但有想象力,你可以定义一个无限长自然数组,

      • from_2 = [2..] #它并不实际发生,当你foreach遍历取值时,真正的求值才发生。
        然后定义过滤器。
      • filter pred #它将一个流转成另一个流,当你foreach遍历时,里面值按需生成
        然后我们滤去这个数组所有被第一个值整除的数
      • filte from_2 (/x -> x / 2 == 0) ,同样,它不是对序列上的所有值立刻发生,
        它得到 [3..]
        重复最后一点,我们得到这样的数列,
        [2..] [3..] [5..] [7..] ...
        很眼熟吧,埃拉托斯特尼筛法, haskell 里算法很直接
        from_2 = [2..]
        sieve list = h : (sieve $ filter (% h) $ t)
        where h = head list
        t = tail list
        first_20_prims = take 20 $ sieve from_2
    • 好吧,我用c#来写一个,

      //[...2,3,4,5,6...]
        IEnumerable<int> from_n(int n)
        {
            int i = n;
            while(true)
                yield return i++;
        }
      
        IEnumerable<int> filter(IEnumerable<int> src, Func<int, bool> pred)
        {
            foreach(var i in src)
                if(pred(i))
                    yield return i;
        }
      
        IEnumerable<int> sieve(IEnumerable<int> src)
        {
            var factor = src.First();
            yield return factor;
            var rest = sieve(filter(src, x => x % factor != 0));
            foreach (var i in rest)
                yield return i;
        }
      
        static void Main(string[] args)
        {
            Program p = new Program();
            var prims = p.sieve(p.from_n(2));
            foreach(var i in prims.Take(1100))
            {
                Console.WriteLine(i);
            }
        }
      

      也许你会说,无论是hakell 或是c#实现,都用到了语言的”特别"支持,但,如果经过了深入思考(lazy), 你也 可以在c/c++ 做类似实现,本来我打算写一个,因为懒惰, 这个问题留给你 (注:这题有难度,提示,实现类似IEnumerator接口的iterator,你写得多短,证明你有多(理解)lazy)

    相关文章

      网友评论

          本文标题:懒惰模式

          本文链接:https://www.haomeiwen.com/subject/dpxgjttx.html