懒惰模式

<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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容