極致之美——百行代碼實(shí)現(xiàn)全新智能語言第2/6頁(yè)
更新時(shí)間: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);
};
我們簡(jiǎn)記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]]]]]]
偶然地我們?cè)谶@兒看到如何寫cond表達(dá)式的缺省子句. 第一個(gè)元素是't的子句總是會(huì)成功的. 于是
[cond,[x,y],[[_,true],z]]
等同于我們?cè)谀承┱Z言中寫的
if x then y else z
對(duì)于函數(shù)調(diào)用,具有如下結(jié)構(gòu):[FunName,[_,args]]
其中FunName是函數(shù)名稱,[_,args]是指定參數(shù)引用列表args
注意[FunName,args]也是合法的,但是和[FunName,[_,args]]有所區(qū)別,對(duì)于前者,指令在被調(diào)用之前先計(jì)算args的值,把計(jì)算出的值作為參數(shù)列表代入函數(shù)計(jì)算(期望args計(jì)算結(jié)果為L(zhǎng)ist),而后者的args參數(shù)列表在函數(shù)指令調(diào)用時(shí)才被計(jì)算
到這里為止我們很高興地看到LispScript已經(jīng)可以不依賴于javascript來擴(kuò)展了
現(xiàn)在我們可以直接用LispScript定義一些新函數(shù)了:
函數(shù):[isNull,x]測(cè)試它的自變量是否是空表.
LispScript.Run(
[defun,'isNull',['x'],
[eq,'x',[_,NIL]]]
);
> [isNull,[_,a]]
[]
> [isNull. [_,[]]]
t
函數(shù):[and,x,y]返回t如果它的兩個(gè)自變量都是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]取兩個(gè)表并返回它們的連結(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è)相同長(zhǎng)度的表,返回一個(gè)由雙元素表構(gòu)成的表,雙元素表是相應(yīng)位置的x,y的元素對(duì).
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中第一個(gè)符合如下條件的表的第二個(gè)元素:它的第一個(gè)元素是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)在的問題是我們必須要定義一個(gè)引用的反函數(shù)f,令[f,[_,e]] = e
而顯然地ret正是這樣一個(gè)函數(shù)
[map,x,y]期望x是原子,y是一個(gè)表,如果[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中的每個(gè)元素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á)式等等.也許算是一個(gè)優(yōu)美的表示法, 那下一步呢? 現(xiàn)在驚喜來了. 我們可以寫一個(gè)函數(shù)作為我們語言的解釋器:此函數(shù)取任意Lisp表達(dá)式作自變量并返回它的值. 如下所示:
LispScript.Run(
[defun,'_eval',['e','a'],
[ret,[maplist,[_,'e'],'a']]
]
);
_eval.的簡(jiǎn)潔程度或許超出了我們?cè)鹊念A(yù)想,于是這樣我們獲得了LispScrip實(shí)現(xiàn)的一個(gè)完整的自身的解析器!
讓我們回過頭考慮一下這意味著什么. 我們?cè)谶@兒得到了一個(gè)非常優(yōu)美的計(jì)算模型. 僅用quote,atom,eq,car,cdr,cons,和cond, 我們定義了函數(shù)_eval.,它事實(shí)上實(shí)現(xiàn)了我們的語言,用它可以定義和(或)動(dòng)態(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如果它的自變量有一個(gè)為t,否則返回[]
LispScript.Run(
[defun,'or',['x','y'],
[not,[and,[not,'x'],[not,'y']]]]
);
增加循環(huán)控制foreach,[foreach,v,[paralist],[expr]]
foreach期望list是一個(gè)表,依次取表中的每一個(gè)原子作為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)了一個(gè)簡(jiǎn)單的向下遞歸的詞法分析器,它能對(duì)嵌套數(shù)組的每個(gè)原子進(jìn)行簡(jiǎn)單處理,加上幾個(gè)輔助函數(shù)(toEvalString(),Assert(),Element()和一個(gè)存放函數(shù)名稱的堆棧...簡(jiǎn)單來說我們僅用了數(shù)十行代碼實(shí)現(xiàn)了一種全新的“函數(shù)式”語言??LispScript的完整內(nèi)核。
接著我們定義了7種原始操作,它們分別是quote,atom,eq,car,cdr,cons和cond
然后(相對(duì)較復(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ù)
最后我們?cè)诖嘶A(chǔ)上定義出一些略為復(fù)雜的函數(shù),它們包括:or,setq,foreach和let,其中一些新函數(shù)帶給我們的新語言定義變量和處理循環(huán)的能力,加上前面實(shí)現(xiàn)的一些函數(shù),一個(gè)比較完善的基礎(chǔ)環(huán)境就搭建成了。
寫在最后:LispScript和Lisp
事實(shí)上我們依照[ref. Paul Graham.]的精彩描述用javascript實(shí)現(xiàn)了LispScript,毫無疑問,它是一種Lisp(或者Lisp風(fēng)格的函數(shù)式語言),盡管功能上還十分簡(jiǎn)陋,但它確實(shí)是符合Lisp的基本思想和擁有Lisp的基本特性。由于javascript數(shù)組文法的特點(diǎn),我用[]取代了[ref. Paul Graham]中的(),用逗號(hào)取代了空格作為分隔符。同[ref. Paul Graham]的文章以及目前一些標(biāo)準(zhǔn)(或者相對(duì)標(biāo)準(zhǔn))的Lisp不同的是,我根據(jù)javascript靈活的特點(diǎn)有意弱化了LispScript的語法結(jié)構(gòu),這樣使得LispScript更加靈活,也更加方便實(shí)現(xiàn),然而代價(jià)是一小部分的可維護(hù)性和安全性。
最后,LispScript還有許多需要完善的內(nèi)容,例如,最明顯地是它基本上還不具有基本的數(shù)值運(yùn)算能力(相對(duì)而言,符號(hào)操作能力已經(jīng)比較完善),另外對(duì)原子操作參數(shù)合法性的檢驗(yàn)、副作用, 連續(xù)執(zhí)行 (它得和副作用在一起才有用), 動(dòng)態(tài)可視域、復(fù)雜數(shù)據(jù)結(jié)構(gòu)支持以及注釋文法(這相當(dāng)重要!)也都是它所欠缺的,不過這些功能“都可以令人驚訝地用極少的額外代碼來補(bǔ)救”。
感謝約翰麥卡錫,這位天才早在數(shù)十年前就向我們展示了一種程序設(shè)計(jì)領(lǐng)域內(nèi)至今無人能超越的“極致的美”,他于1960年發(fā)表了一篇非凡的論文,他在這篇論文中對(duì)編程的貢獻(xiàn)有如歐幾里德對(duì)幾何的貢獻(xiàn).1 他向我們展示了,在只給定幾個(gè)簡(jiǎn)單的操作符和一個(gè)表示函數(shù)的記號(hào)的基礎(chǔ)上, 如何構(gòu)造出一個(gè)完整的編程語言. 麥卡錫稱這種語言為L(zhǎng)isp, 意為L(zhǎng)ist Processing, 因?yàn)樗闹饕枷胫皇怯靡环N簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)表(list)來代表代碼和數(shù)據(jù).
感謝保羅格雷厄姆,他用淺顯易懂的語言將Lisp的根源和實(shí)質(zhì)展現(xiàn)在我們面前,令我們能夠幸運(yùn)地零距離體驗(yàn)Lisp的這種“超凡的美”
如果你理解了約翰麥卡錫的eval, 那你就不僅僅是理解了程序語言歷史中的一個(gè)階段. 這些思想至今仍是Lisp的語義核心. 所以從某種意義上, 學(xué)習(xí)約翰麥卡錫的原著向我們展示了Lisp究竟是什么. 與其說Lisp是麥卡錫的設(shè)計(jì),不如說是他的發(fā)現(xiàn). 它不是生來就是一門用于人工智能, 快速原型開發(fā)或同等層次任務(wù)的語言. 它是你試圖公理化計(jì)算的結(jié)果(之一).
隨著時(shí)間的推移, 中級(jí)語言, 即被中間層程序員使用的語言, 正一致地向Lisp靠近. 因此通過理解eval你正在明白將來的主流計(jì)算模式會(huì)是什么樣.
References
The Roots of Lisp Paul Graham. Draft, January 18, 2002.
LISt Primer Colin Allen & Maneesh Dhagat.Tue Feb 6, 2001.(
相關(guān)文章
微信小程序的數(shù)據(jù)存儲(chǔ)與Django等服務(wù)發(fā)送請(qǐng)求?講解
這篇文章主要為大家介紹了微信小程序的數(shù)據(jù)存儲(chǔ)與Django等服務(wù)發(fā)送請(qǐng)求講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04
js中根據(jù)字?jǐn)?shù)截取字符串,不能截?cái)鄒rl
給一個(gè)文字,對(duì)輸出的文字進(jìn)行截取,保留400個(gè)字符,其中對(duì)url的保留比較麻煩,尤其是有兩個(gè)相同url時(shí)不能采用indexOf獲取其字符位置2012-01-01
AJAX XMLHttpRequest對(duì)象創(chuàng)建使用詳解
這篇文章主要介紹了AJAX XMLHttpRequest對(duì)象創(chuàng)建使用詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
利用JS如何計(jì)算字符串所占字節(jié)數(shù)示例代碼
因?yàn)樽罱?xiàng)目有個(gè)需求要用js計(jì)算一串字符串寫入到localStorage里所占的內(nèi)存,所以便有了這篇文章,下面這篇文章主要給大家介紹了關(guān)于利用JS如何計(jì)算字符串所占字節(jié)數(shù)的相關(guān)資料,需要的朋友可以參考下。2017-09-09
js實(shí)現(xiàn)拖拽效果(構(gòu)造函數(shù))
這篇文章主要介紹了js利用構(gòu)造函數(shù)實(shí)現(xiàn)拖拽效果2015-12-12
javascript之querySelector和querySelectorAll使用說明
其實(shí)關(guān)于querySelector和querySelectorAll的介紹說明很多,在此主要是做個(gè)記錄2011-10-10

