<i>"懶惰、傲慢、缺乏耐性是程序員的三大美德"——Larry Wall(Perl's father)</i>
很久以前,《設(shè)計模式》是本“紅寶書“之類的讀物,你要是面試時談些"模式"總會有加分,同事有時也說,"哦,這里是個singleton模式, 那里是個clone模式"。今天,我們不怎么談模式,遇到問題時,總結(jié)出一些套路,有時套路有用,有時套路卻行不通,成為阻礙。
我想,程序設(shè)計里“思而不學(xué)則die, 學(xué)而不思則money",是一個普遍問題,因此,我有點打算,介紹一些更為簡單的”模式”,”簡單“但能引起"本質(zhì)”思考,希望這些”簡單“的模式能夠幫助你多思考。
模式之——懶惰
c語言允許這樣的語句
my_assert = (true | (1/0)>millon);
我們知道(1/0)根本不會發(fā)生,這被稱為短路求值,三目運算符,類似
gain = stupid_test ? million : (1/0);-
嫌 if 太啰嗦,三目又不好看,有人希望寫一個漂亮的inline函數(shù),
int my_if(test, trueValue, falseValue) { if(test) trueValue else falseValue; } //usage gain = my_if(stupid_test, 1000000, (1/0) )
然而,他發(fā)現(xiàn)這樣根本不行!(1/0)總是被求值!
-
scala的解決方案,
def my_if(p: boolean, true_value : =>int, false_value : => int) gain = my_if(stupid_test, 1000000, (1/0) ) //perfect
解釋:
=>int是一個表示這里是函數(shù)類型,輸入為空,返回為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模式說的是,按需求值,只有在必要時,計算才發(fā)生
你可能已經(jīng)發(fā)現(xiàn),傳入函數(shù)是實現(xiàn)lazy的關(guān)鍵
上面是些無用的例子,說些實際的例子吧:
-
log 系統(tǒng)
在c#, java一類代碼里面, 我們常常有c語言里不存在的煩惱(因為沒有宏),比如說,
內(nèi)循環(huán)里,做log是一件需要很小心的事,因為我們常常寫成這種形式,
if(log_level > LOG_DEBUG) log.e( string.format("state: %d, %d, %d", getx(), gety(), getz()) )在未打開調(diào)試開關(guān)時,這里只造成一個 if的開銷,如果沒有<i>if</i>, <i>string.format</i> 總會發(fā)生,造成有影響的開銷,但寫起來還是麻煩的。
解決辦法呢,almost no,一個比較丑陋,但有效的辦法是:
void lazy_log(debug_lv, Func<string> cont)
{
if(log_level > LOG_DEBUG) log.w(cont());
}
//usage,未開調(diào)試前,format不會發(fā)生
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(); //第一次發(fā)生
return _ins;
}
首次getInstance時,將實例初始化,這也是一個按需求值的例子,
我們想象一個有很多module的系統(tǒng),系統(tǒng)之間存在依賴性,比如說moduleA, 需要moduleB先啟動,
有時我們顯式地去調(diào)用它.
void system_ini()
{
ModuleA.init()
ModuleB.init()
...
}
這樣的話,我們需要顯式維護初始化過程,但使用單例,我們只需要getInstance,讓正確的初始化順序自動發(fā)生!
衍生出的 memory 模式
在求值時,我們總在第一次求值,之后將之存于字典(而不先將之計算存于字典),這是一種空間與時間的均衡的技術(shù),
int hard_calc(int i)
{
if(_cache.find(i) ) return _cache.get(i);//
int result;
////very hard word...
_cache.add(i, result);
return result;
}
如果你做過游戲物件管理,這種模式到處可見。-
懶惰的本質(zhì),是按需求值
在大多數(shù)編程語言里,傳入函數(shù)的參數(shù),是一種嚴(yán)格求值,但并非是所有語言都是嚴(yán)格求值,但,還有一類語言,具有不同的求值策略,比如說 haskell,懶惰,但有想象力,你可以定義一個無限長自然數(shù)組,
- from_2 = [2..] #它并不實際發(fā)生,當(dāng)你foreach遍歷取值時,真正的求值才發(fā)生。
然后定義過濾器。 - filter pred #它將一個流轉(zhuǎn)成另一個流,當(dāng)你foreach遍歷時,里面值按需生成
然后我們?yōu)V去這個數(shù)組所有被第一個值整除的數(shù) - filte from_2 (/x -> x / 2 == 0) ,同樣,它不是對序列上的所有值立刻發(fā)生,
它得到 [3..]
重復(fù)最后一點,我們得到這樣的數(shù)列,
[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
- from_2 = [2..] #它并不實際發(fā)生,當(dāng)你foreach遍歷取值時,真正的求值才發(fā)生。
-
好吧,我用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#實現(xiàn),都用到了語言的”特別"支持,但,如果經(jīng)過了深入思考(lazy), 你也 可以在c/c++ 做類似實現(xiàn),本來我打算寫一個,因為懶惰, 這個問題留給你 (注:這題有難度,提示,實現(xiàn)類似IEnumerator接口的iterator,你寫得多短,證明你有多(理解)lazy)