極致之美——百行代碼實(shí)現(xiàn)全新智能語言第2/6頁
更新時間:2007年03月14日 00:00:00 作者:
我們可以這樣表示此函數(shù)
[label,subst,[lambda,[x,y,z],
[cond,[[atom,z],
[cond,[[eq,z,y],x],
true,z]]],
[true,[cons,[subst,x,y,[car,z]],
[subst,x,y,[cdr,z]]]]]]]
label = function(funName, funDef)
{
__funList.push(funName);
return LispScript.Run(funDef);
};
我們簡記f=[label,f,[lambda,[...],e]]為
[defun,f,[...],e]
defun = function(funName, args, code)
{
__funList.push(funName);
if(code instanceof Array)
{
var fun = new Function(args,
"for(var i = 0; i < arguments.length; i++) arguments[i] = LispScript.Run(arguments[i]);return LispScript.Run("+code.toEvalString()+");");
var globalFuncName = __funList.pop();
fun._funName = globalFuncName;
if(globalFuncName != null)
self[globalFuncName] = fun;
return fun;
}
return [];
};
于是
[defun,subst,[x,y,z],
[cond,[[atom,z],
[cond,[[eq,z,y],x],
[true,z]]],
[true,[cons,[subst,x,y,[car,z]],
[subst,x,y,[cdr,z]]]]]]
偶然地我們在這兒看到如何寫cond表達(dá)式的缺省子句. 第一個元素是't的子句總是會成功的. 于是
[cond,[x,y],[[_,true],z]]
等同于我們在某些語言中寫的
if x then y else z
對于函數(shù)調(diào)用,具有如下結(jié)構(gòu):[FunName,[_,args]]
其中FunName是函數(shù)名稱,[_,args]是指定參數(shù)引用列表args
注意[FunName,args]也是合法的,但是和[FunName,[_,args]]有所區(qū)別,對于前者,指令在被調(diào)用之前先計(jì)算args的值,把計(jì)算出的值作為參數(shù)列表代入函數(shù)計(jì)算(期望args計(jì)算結(jié)果為List),而后者的args參數(shù)列表在函數(shù)指令調(diào)用時才被計(jì)算
到這里為止我們很高興地看到LispScript已經(jīng)可以不依賴于javascript來擴(kuò)展了
現(xiàn)在我們可以直接用LispScript定義一些新函數(shù)了:
函數(shù):[isNull,x]測試它的自變量是否是空表.
LispScript.Run(
[defun,'isNull',['x'],
[eq,'x',[_,NIL]]]
);
> [isNull,[_,a]]
[]
> [isNull. [_,[]]]
t
函數(shù):[and,x,y]返回t如果它的兩個自變量都是t, 否則返回[].
LispScript.Run(
[defun,'and',['x','y'],
[cond,['x',[cond,['y',true],[true,NIL]],
[true,NIL]]]]
);
> [and,[atom,[_,a]],[eq,[_,a],[_,a]]]
t
> [and,[atom,[_,a]],[eq,[_,a],[_,b]]]
[]
函數(shù):[not,x]返回t如果它的自變量返回[],返回[]如果它的自變量返回t.
LispScript.Run(
[defun,'not',['x'],
[cond,['x',NIL],
[true,true]]]
);
> [not,[eq,[_,a],[_,a]]]
[]
> [not,[eq,[_,a],[_,b]]]
t
函數(shù):[append,x,y]取兩個表并返回它們的連結(jié).
LispScript.Run(
[defun,'append',['x','y'],
[cond,[[isNull,'x'],'y'],
[true,[cons,[car,'x'],['append',[cdr,'x'],'y']]]]]
);
> [append,[_,[a,b]],[_,[c,d]]]
[a,b,c,d]
> [append,[], [_,[c,d]]]
[c,d]
函數(shù):[pair,x,y]取兩個相同長度的表,返回一個由雙元素表構(gòu)成的表,雙元素表是相應(yīng)位置的x,y的元素對.
LispScript.Run(
[defun,'pair',['x','y'],
[cond,
[[and,[isNull,'x'],[isNull,'y']],NIL],
[[and,[not,[atom,'x']],[not,[atom,'y']]],
[append,[[[car,'x'],[car,'y']]],['pair',[cdr,'x'],[cdr,'y']]]
]]]
);
> [pair,[_,[x,y,z]],[_,[a,b,c]]]
[[x,a],[y,b],[z,c]]
[assoc,x,y]取原子x和形如pair函數(shù)所返回的表y,返回y中第一個符合如下條件的表的第二個元素:它的第一個元素是x.
LispScript.Run(
[defun,'assoc',['x','y'],
[cond,[[eq,[car,[car,'y']],'x'],[car,[cdr,[car,'y']]]],
[[isNull,'y'],NIL],[true,['assoc','x',[cdr,'y']]]]]
);
> [assoc,[_,x],[_,[[x,a],[y,b]]]]
a
> [assoc,[_,x],[_,[[x,new],[x,a],[y,b]]]]
new
[ret,e]返回表達(dá)式計(jì)算結(jié)果
LispScript.Run(
[defun,'ret',['e'],[car,['e']]]
);
[str,e]返回表達(dá)式計(jì)算結(jié)果的引用
LispScript.Run(
[defun,'str',['e'],[_,[_,'e']]]
);
我們來看一下為什么要定義ret函數(shù):
我想通過前面的解釋和實(shí)際應(yīng)用大家已經(jīng)理解了引用(quote)的重要性,并且很容易證明:[[_,e]] = [e]
現(xiàn)在的問題是我們必須要定義一個引用的反函數(shù)f,令[f,[_,e]] = e
而顯然地ret正是這樣一個函數(shù)
[map,x,y]期望x是原子,y是一個表,如果[assoc,x,y]非空返回[assoc,x,y]的值否則返回x
LispScript.Run(
[defun,'map',['x','y'],
[cond,[[isNull,[assoc,'x','y']],'x'],[true,[assoc,'x','y']]]]
);
[maplist,x,y]期望x和y都是表,返回由x中的每個元素t求[map,t,y]的結(jié)果構(gòu)成的表
LispScript.Run(
[defun,'maplist',['x','y'],
[cond,
[[atom,[_,'x']],[map,'x','y']],
[true,[cons,['maplist',[car,[_,'x']],'y'],['maplist',[cdr,[_,'x']],'y']]]
]
]
);
因此我們能夠定義函數(shù)來連接表,替換表達(dá)式等等.也許算是一個優(yōu)美的表示法, 那下一步呢? 現(xiàn)在驚喜來了. 我們可以寫一個函數(shù)作為我們語言的解釋器:此函數(shù)取任意Lisp表達(dá)式作自變量并返回它的值. 如下所示:
LispScript.Run(
[defun,'_eval',['e','a'],
[ret,[maplist,[_,'e'],'a']]
]
);
_eval.的簡潔程度或許超出了我們原先的預(yù)想,于是這樣我們獲得了LispScrip實(shí)現(xiàn)的一個完整的自身的解析器!
讓我們回過頭考慮一下這意味著什么. 我們在這兒得到了一個非常優(yōu)美的計(jì)算模型. 僅用quote,atom,eq,car,cdr,cons,和cond, 我們定義了函數(shù)_eval.,它事實(shí)上實(shí)現(xiàn)了我們的語言,用它可以定義和(或)動態(tài)生成任何我們想要的額外的函數(shù)和各種文法(這一點(diǎn)比較重要)
其他(略為復(fù)雜)的擴(kuò)展:
下面我們定義變量的賦值操作[setq,paraName,paraValue]
LispScript.Run(
[defun,'setq',['para','val'],
[ret,[defun,'para',[],[_eval,'val']]]]
);
增加邏輯操作符or,[or,x,y]返回t如果它的自變量有一個為t,否則返回[]
LispScript.Run(
[defun,'or',['x','y'],
[not,[and,[not,'x'],[not,'y']]]]
);
增加循環(huán)控制foreach,[foreach,v,[paralist],[expr]]
foreach期望list是一個表,依次取表中的每一個原子作為expr的參數(shù)進(jìn)行計(jì)算,返回計(jì)算結(jié)果的表
LispScript.Run(
[defun,'foreach',['v','list','expr'],
[cond,
[[isNull,'list'],[]],
[true,[cons,[_eval,[_,'expr'],[['v',[car,'list']]]],['foreach','v',[cdr,'list'],[_,'expr']]]]
]
]
);
增加批量賦值操作let,[let,[[a1,v1],[a2,v2]...]]
LispScript.Run(
[defun,'let',['paralist'],
[foreach,"'v'",'paralist',[_,[setq,[car,"'v'"],[car,[cdr,"'v'"]]]]]
]
);
總結(jié)
現(xiàn)在該回過頭來看看我們究竟做了什么,以及這么做有什么意義了。
首先我們用javascript實(shí)現(xiàn)了一個簡單的向下遞歸的詞法分析器,它能對嵌套數(shù)組的每個原子進(jìn)行簡單處理,加上幾個輔助函數(shù)(toEvalString(),Assert(),Element()和一個存放函數(shù)名稱的堆棧...簡單來說我們僅用了數(shù)十行代碼實(shí)現(xiàn)了一種全新的“函數(shù)式”語言??LispScript的完整內(nèi)核。
接著我們定義了7種原始操作,它們分別是quote,atom,eq,car,cdr,cons和cond
然后(相對較復(fù)雜地),我們定義了三種用來描述和調(diào)用函數(shù)的標(biāo)記,它們分別是lambda, label以及defun,于是我們成功地用另外不到百行代碼實(shí)現(xiàn)了LispScript語言的核心環(huán)境。
接著(接下來的部分已經(jīng)可以完全獨(dú)立于javascript)我們用7種原始操作符和函數(shù)定義標(biāo)記defun定義出一些新的函數(shù),分別是:isNull,and,not,append,pair,assoc,ret和str
然后我們驚喜地發(fā)現(xiàn),可以僅用一行LispScript指令定義出自身的“解析器”??_eval函數(shù)
最后我們在此基礎(chǔ)上定義出一些略為復(fù)雜的函數(shù),它們包括:or,setq,foreach和let,其中一些新函數(shù)帶給我們的新語言定義變量和處理循環(huán)的能力,加上前面實(shí)現(xiàn)的一些函數(shù),一個比較完善的基礎(chǔ)環(huán)境就搭建成了。
寫在最后:LispScript和Lisp
事實(shí)上我們依照[ref. Paul Graham.]的精彩描述用javascript實(shí)現(xiàn)了LispScript,毫無疑問,它是一種Lisp(或者Lisp風(fēng)格的函數(shù)式語言),盡管功能上還十分簡陋,但它確實(shí)是符合Lisp的基本思想和擁有Lisp的基本特性。由于javascript數(shù)組文法的特點(diǎn),我用[]取代了[ref. Paul Graham]中的(),用逗號取代了空格作為分隔符。同[ref. Paul Graham]的文章以及目前一些標(biāo)準(zhǔn)(或者相對標(biāo)準(zhǔn))的Lisp不同的是,我根據(jù)javascript靈活的特點(diǎn)有意弱化了LispScript的語法結(jié)構(gòu),這樣使得LispScript更加靈活,也更加方便實(shí)現(xiàn),然而代價是一小部分的可維護(hù)性和安全性。
最后,LispScript還有許多需要完善的內(nèi)容,例如,最明顯地是它基本上還不具有基本的數(shù)值運(yùn)算能力(相對而言,符號操作能力已經(jīng)比較完善),另外對原子操作參數(shù)合法性的檢驗(yàn)、副作用, 連續(xù)執(zhí)行 (它得和副作用在一起才有用), 動態(tài)可視域、復(fù)雜數(shù)據(jù)結(jié)構(gòu)支持以及注釋文法(這相當(dāng)重要!)也都是它所欠缺的,不過這些功能“都可以令人驚訝地用極少的額外代碼來補(bǔ)救”。
感謝約翰麥卡錫,這位天才早在數(shù)十年前就向我們展示了一種程序設(shè)計(jì)領(lǐng)域內(nèi)至今無人能超越的“極致的美”,他于1960年發(fā)表了一篇非凡的論文,他在這篇論文中對編程的貢獻(xiàn)有如歐幾里德對幾何的貢獻(xiàn).1 他向我們展示了,在只給定幾個簡單的操作符和一個表示函數(shù)的記號的基礎(chǔ)上, 如何構(gòu)造出一個完整的編程語言. 麥卡錫稱這種語言為Lisp, 意為List Processing, 因?yàn)樗闹饕枷胫皇怯靡环N簡單的數(shù)據(jù)結(jié)構(gòu)表(list)來代表代碼和數(shù)據(jù).
感謝保羅格雷厄姆,他用淺顯易懂的語言將Lisp的根源和實(shí)質(zhì)展現(xiàn)在我們面前,令我們能夠幸運(yùn)地零距離體驗(yàn)Lisp的這種“超凡的美”
如果你理解了約翰麥卡錫的eval, 那你就不僅僅是理解了程序語言歷史中的一個階段. 這些思想至今仍是Lisp的語義核心. 所以從某種意義上, 學(xué)習(xí)約翰麥卡錫的原著向我們展示了Lisp究竟是什么. 與其說Lisp是麥卡錫的設(shè)計(jì),不如說是他的發(fā)現(xiàn). 它不是生來就是一門用于人工智能, 快速原型開發(fā)或同等層次任務(wù)的語言. 它是你試圖公理化計(jì)算的結(jié)果(之一).
隨著時間的推移, 中級語言, 即被中間層程序員使用的語言, 正一致地向Lisp靠近. 因此通過理解eval你正在明白將來的主流計(jì)算模式會是什么樣.
References
The Roots of Lisp Paul Graham. Draft, January 18, 2002.
LISt Primer Colin Allen & Maneesh Dhagat.Tue Feb 6, 2001.(
相關(guān)文章
JS設(shè)計(jì)模式之?dāng)?shù)據(jù)訪問對象模式的實(shí)例講解
下面小編就為大家?guī)硪黄狫S設(shè)計(jì)模式之?dāng)?shù)據(jù)訪問對象模式的實(shí)例講解。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09Javascript 實(shí)現(xiàn)微信分享(QQ、朋友圈、分享給朋友)
這篇文章主要介紹了Javascript 實(shí)現(xiàn)微信分享(QQ、朋友圈、分享給朋友)的相關(guān)資料,需要的朋友可以參考下2016-10-10JavaScript實(shí)現(xiàn)左右滾動電影畫布
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)左右滾動電影畫布,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2020-02-02