着墨中文lisp登入_Lisp的本质 - climbdream的个人空间 - OSCHINA - 中文开源技术交流社区...
又簡單又有效率, 也不需要另外的預(yù)處理語言。我們可以在編譯時就充分發(fā)揮宿主語言(此處是C/C++)的強大能力, 我們可以很容易地在編譯時連接數(shù)據(jù)庫, 建立數(shù)據(jù)訪問層, 就像JSP或者ASP創(chuàng)建網(wǎng)頁那樣。我們也用不著專門的窗口工具來另外建立工程。我們可以在代碼中立即加入必要的工具。我們也用不著顧慮建立這種工具是不 是值得, 因為這太容易了, 太簡單了。這樣子不知可以節(jié)省多少時間啊。
你好, Lisp
到此刻為止, 我們所知的關(guān)于Lisp的指示可以總結(jié)為一句話: Lisp是一個可執(zhí)行的語法更優(yōu)美的XML, 但我們還沒有說Lisp是怎樣做到這一點的, 現(xiàn)在開始補上這個話題。
Lisp有豐富的內(nèi)置數(shù)據(jù)類型, 其中的整數(shù)和字符串和其他語言沒什么分別。像71或者"hello"這樣的值, 含義也和C++或者Java這樣的語言大體相同。真正有意思的三種類型是符號(symbol), 表和函數(shù)。這一章的剩余部分, 我都會用來介紹這幾種類型, 還要介紹Lisp環(huán)境是怎樣編譯和運行源碼的。這個過程用Lisp的術(shù)語來說通常叫做求值。通讀這一節(jié)內(nèi)容, 對于透徹理解元編程的真正潛力, 以及代碼和數(shù)據(jù)的同一性, 和面向領(lǐng)域語言的觀念, 都極其重要。萬勿等閑視之。我會盡量講得生動有趣一些, 也希望你能獲得一些啟發(fā)。那好, 我們先講符號。
大體上, 符號相當(dāng)于C++或Java語言中的標(biāo)志符, 它的名字可以用來訪問變量值(例如currentTime, arrayCount, n, 等等), 差別在于, Lisp中的符號更加基本。在C++或Java里面, 變量名只能用字母和下劃線的組合, 而Lisp的符號則非常有包容性, 比如, 加號(+)就是一個合法的符號, 其他的像-, =, hello-world, *等等都可以是符號名。符號名的命名規(guī)則可以在網(wǎng)上查到。你可以給這些符號任意賦值, 我們這里先用偽碼來說明這一點。假定函數(shù)set是給變量賦值(就像等號=在C++和Java里的作用), 下面是我們的例子:
set(test, 5)??????????? // 符號test的值為5
set(=, 5)?????????????? // 符號=的值為5
set(test, "hello")????? // 符號test的值為字符串"hello"
set(test, =)??????????? // 此時符號=的值為5, 所以test的也為5
set(*, "hello")???????? // 符號*的值為"hello"
好像有什么不對的地方? 假定我們對*賦給整數(shù)或者字符串值, 那做乘法時怎么辦? 不管怎么說, *總是乘法呀? 答案簡單極了。Lisp中函數(shù)的角色十分特殊, 函數(shù)也是一種數(shù)據(jù)類型, 就像整數(shù)和字符串一樣, 因此可以把它賦值給符號。乘法函數(shù)Lisp的內(nèi)置函數(shù), 默認賦給*, 你可以把其他函數(shù)賦值給*, 那樣*就不代表乘法了。你也可以把這函數(shù)的值存到另外的變量里。我們再用偽碼來說明一下:
*(3,4)????????? // 3乘4, 結(jié)果是12
set(temp, *)??? // 把*的值, 也就是乘法函數(shù), 賦值給temp
set(*, 3)?????? // 把3賦予*
*(3,4)????????? // 錯誤的表達式, *不再是乘法, 而是數(shù)值3
temp(3,4)?????? // temp是乘法函數(shù), 所以此表達式的值為3乘4等于12
set(*, temp)??? // 再次把乘法函數(shù)賦予*
*(3,4)????????? // 3乘4等于12
再古怪一點, 把減號的值賦給加號:
set(+, -)?????? // 減號(-)是內(nèi)置的減法函數(shù)
+(5, 4)???????? // 加號(+)現(xiàn)在是代表減法函數(shù), 結(jié)果是5減4等于1
這只是舉例子, 我還沒有詳細講函數(shù)。Lisp中的函數(shù)是一種數(shù)據(jù)類型, 和整數(shù), 字符串,符號等等一樣。一個函數(shù)并不必然有一個名字, 這和C++或者Java語言的情形很不相同。在這里函數(shù)自己代表自己。事實上它是一個指向代碼塊的指針, 附帶有一些其他信息(例如一組參數(shù)變量)。只有在把函數(shù)賦予其他符號時, 它才具有了名字, 就像把一個數(shù)值或字符串賦予變量一樣的道理。你可以用一個內(nèi)置的專門用于創(chuàng)建函數(shù)的函數(shù)來創(chuàng)建函數(shù),然后把它賦值給符號fn, 用偽碼來表示就是:
fn [a]
{
return *(a, 2);
}
這段代碼返回一個具有一個參數(shù)的函數(shù), 函數(shù)的功能是計算參數(shù)乘2的結(jié)果。這個函數(shù)還沒有名字, 你可以把此函數(shù)賦值給別的符號:
set(times-two, fn [a] {return *(a, 2)})
我們現(xiàn)在可以這樣調(diào)用這個函數(shù):
time-two(5)???????? // 返回10
我們先跳過符號和函數(shù), 講一講表。什么是表? 你也許已經(jīng)聽過好多相關(guān)的說法。表, 一言以蔽之, 就是把類似XML那樣的數(shù)據(jù)塊, 用s表達式來表示。表用一對括號括住, 表中元素以空格分隔, 表可以嵌套。例如(這回我們用真正的Lisp語法, 注意用分號表示注釋):
()????????????????????? ; 空表
(1)???????????????????? ; 含一個元素的表
(1 "test")????????????? ; 兩元素表, 一個元素是整數(shù)1, 另一個是字符串
(test "hello")????????? ; 兩元素表, 一個元素是符號, 另一個是字符串
(test (1 2) "hello")??? ; 三元素表, 一個符號test, 一個含有兩個元素1和2的
; 表, 最后一個元素是字符串
當(dāng)Lisp系統(tǒng)遇到這樣的表時, 它所做的, 和Ant處理XML數(shù)據(jù)所做的, 非常相似, 那就是試圖執(zhí)行它們。其實, Lisp源碼就是特定的一種表, 好比Ant源碼是一種特定的XML一樣。Lisp執(zhí)行表的順序是這樣的, 表的第一個元素當(dāng)作函數(shù), 其他元素當(dāng)作函數(shù)的參數(shù)。如果其中某個參數(shù)也是表, 那就按照同樣的原則對這個表求值, 結(jié)果再傳遞給最初的函數(shù)作為參數(shù)。這就是基本原則。我們看一下真正的代碼:
(* 3 4)???????????????? ; 相當(dāng)于前面列舉過的偽碼*(3,4), 即計算3乘4
(times-two 5)?????????? ; 返回10, times-two按照前面的定義是求參數(shù)的2倍
(3 4)?????????????????? ; 錯誤, 3不是函數(shù)
(time-two)????????????? ; 錯誤, times-two要求一個參數(shù)
(times-two 3 4)???????? ; 錯誤, times-two只要求一個參數(shù)
(set + -)?????????????? ; 把減法函數(shù)賦予符號+
(+ 5 4)???????????????? ; 依據(jù)上一句的結(jié)果, 此時+表示減法, 所以返回1
(* 3 (+ 2 2))?????????? ; 2+2的結(jié)果是4, 再乘3, 結(jié)果是12
上述的例子中, 所有的表都是當(dāng)作代碼來處理的。怎樣把表當(dāng)作數(shù)據(jù)來處理呢? 同樣的,設(shè)想一下, Ant是把XML數(shù)據(jù)當(dāng)作自己的參數(shù)。在Lisp中, 我們給表加一個前綴'來表示數(shù)據(jù)。
(set test '(1 2))?????? ; test的值為兩元素表
(set test (1 2))??????? ; 錯誤, 1不是函數(shù)
(set test '(* 3 4))???? ; test的值是三元素表, 三個元素分別是*, 3, 4
我們可以用一個內(nèi)置的函數(shù)head來返回表的第一個元素, tail函數(shù)來返回剩余元素組成的表。
(head '(* 3 4))???????? ; 返回符號*
(tail '(* 3 4))???????? ; 返回表(3 4)
(head (tal '(* 3 4)))?? ; 返回3
(head test)???????????? ; 返回*
你可以把Lisp的內(nèi)置函數(shù)想像成Ant的任務(wù)。差別在于, 我們不用在另外的語言中擴展Lisp(雖然完全可以做得到), 我們可以用Lisp自己來擴展自己, 就像上面舉的times-two函數(shù)的例子。Lisp的內(nèi)置函數(shù)集十分精簡, 只包含了十分必要的部分。剩下的函數(shù)都是作為標(biāo)準(zhǔn)庫來實現(xiàn)的。
Lisp宏
我們已經(jīng)看到, 元編程在一個類似jsp的模板引擎方面的應(yīng)用。我們通過簡單的字符串處理來生成代碼。但是我們可以做的更好。我們先提一個問題, 怎樣寫一個工具, 通過查找目錄結(jié)構(gòu)中的源文件來自動生成Ant腳本。
用字符串處理的方式生成Ant腳本是一種簡單的方式。當(dāng)然, 還有一種更加抽象, 表達能力更強, 擴展性更好的方式, 就是利用XML庫在內(nèi)存中直接生成XML節(jié)點, 這樣的話內(nèi)存中的節(jié)點就可以自動序列化成為字符串。不僅如此, 我們的工具還可以分析這些節(jié)點, 對已有的XML文件做變換。通過直接處理XML節(jié)點。我們可以超越字符串處理, 使用更高層次的概念, 因此我們的工作就會做的更快更好。
我們當(dāng)然可以直接用Ant自身來處理XML變換和制作代碼生成工具?;蛘呶覀円部梢杂肔isp來做這項工作。正像我們以前所知的, 表是Lisp內(nèi)置的數(shù)據(jù)結(jié)構(gòu), Lisp含有大量的工具來快速有效的操作表(head和tail是最簡單的兩個)。而且, Lisp沒有語義約束, 你可以構(gòu)造任何數(shù)據(jù)結(jié)構(gòu), 只要你原意。
Lisp通過宏(macro)來做元編程。我們寫一組宏來把任務(wù)列表(to-do list)轉(zhuǎn)換為專用領(lǐng)域語言。
回想一下上面to-do list的例子, 其XML的數(shù)據(jù)格式是這樣的:
Clean the hose
Wash the dishes
Buy more soap
相應(yīng)的s表達式是這樣的:
(todo "housework"
(item (priority high) "Clean the house")
(item (priority medium) "Wash the dishes")
(item (priority medium) "Buy more soap"))
假設(shè)我們要寫一個任務(wù)表的管理程序, 把任務(wù)表數(shù)據(jù)存到一組文件里, 當(dāng)程序啟動時, 從文件讀取這些數(shù)據(jù)并顯示給用戶。在別的語言里(比如說Java), 這個任務(wù)該怎么做? 我們會解析XML文件, 從中得出任務(wù)表數(shù)據(jù), 然后寫代碼遍歷XML樹, 再轉(zhuǎn)換為Java的數(shù)據(jù)結(jié)構(gòu)(老實講, 在Java里解析XML真不是件輕松的事情), 最后再把數(shù)據(jù)展示給用戶?,F(xiàn)在如果用Lisp, 該怎么做?
假定要用同樣思路的化, 我們大概會用Lisp庫來解析XML。XML對我們來說就是一個Lisp的表(s表達式), 我們可以遍歷這個表, 然后把相關(guān)數(shù)據(jù)提交給用戶??墒? 既然我們用Lisp, 就根本沒有必要再用XML格式保存數(shù)據(jù), 直接用s表達式就好了, 這樣就沒有必要做轉(zhuǎn)換了。我們也用不著專門的解析庫, Lisp可以直接在內(nèi)存里處理s表達式。注意, Lisp編譯器和.net編譯器一樣, 對Lisp程序來說, 在運行時總是隨時可用的。
但是還有更好的辦法。我們甚至不用寫表達式來存儲數(shù)據(jù), 我們可以寫宏, 把數(shù)據(jù)當(dāng)作代碼來處理。那該怎么做呢? 真的簡單。回想一下, Lisp的函數(shù)調(diào)用格式:
(function-name arg1 arg2 arg3)
其中每個參數(shù)都是s表達式, 求值以后, 傳遞給函數(shù)。如果我們用(+ 4 5)來代替arg1,那么, 程序會先求出結(jié)果, 就是9, 然后把9傳遞給函數(shù)。宏的工作方式和函數(shù)類似。主要的差別是, 宏的參數(shù)在代入時不求值。
(macro-name (+ 4 5))
這里, (+ 4 5)作為一個表傳遞給宏, 然后宏就可以任意處理這個表, 當(dāng)然也可以對它求值。宏的返回值是一個表, 然后有程序作為代碼來執(zhí)行。宏所占的位置, 就被替換為這個結(jié)果代碼。我們可以定義一個宏把數(shù)據(jù)替換為任意代碼, 比方說, 替換為顯示數(shù)據(jù)給用戶的代碼。
這和元編程, 以及我們要做的任務(wù)表程序有什么關(guān)系呢? 實際上, 編譯器會替我們工作,調(diào)用相應(yīng)的宏。我們所要做的, 僅僅是創(chuàng)建一個把數(shù)據(jù)轉(zhuǎn)換為適當(dāng)代碼的宏。
例如, 上面曾經(jīng)將過的C的求三次方的宏, 用Lisp來寫是這樣子:
(defmacro triple (x)
`(+ ~x ~x ~x))
(譯注: 在Common Lisp中, 此處的單引號應(yīng)當(dāng)是反單引號, 意思是對表不求值, 但可以對表中某元素求值, 記號~表示對元素x求值, 這個求值記號在Common Lisp中應(yīng)當(dāng)是逗號。反單引號和單引號的區(qū)別是, 單引號標(biāo)識的表, 其中的元素都不求值。這里作者所用的記號是自己發(fā)明的一種Lisp方言Blaise, 和common lisp略有不同, 事實上, 發(fā)明方言是lisp高手獨有的樂趣, 很多狂熱分子都熱衷這樣做。比如Paul Graham就發(fā)明了ARC, 許多記號比傳統(tǒng)的Lisp簡潔得多, 顯得比較現(xiàn)代)
單引號的用處是禁止對表求值。每次程序中出現(xiàn)triple的時候,
(triple 4)
都會被替換成:
(+ 4 4 4)
我們可以為任務(wù)表程序?qū)懸粋€宏, 把任務(wù)數(shù)據(jù)轉(zhuǎn)換為可執(zhí)行碼, 然后執(zhí)行。假定我們的輸出是在控制臺:
(defmacro item (priority note)
`(block
(print stdout tab "Prority: " ~(head (tail priority)) endl)
(print stdout tab "Note: " ~note endl endl)))
我們創(chuàng)造了一個非常小的有限的語言來管理嵌在Lisp中的任務(wù)表。這個語言只用來解決特定領(lǐng)域的問題, 通常稱之為DSLs(特定領(lǐng)域語言, 或?qū)S妙I(lǐng)域語言)。
特定領(lǐng)域語言
本文談到了兩個特定領(lǐng)域語言, 一個是Ant, 處理軟件構(gòu)造。一個是沒起名字的, 用于處理任務(wù)表。兩者的差別在于, Ant是用XML, XML解析器, 以及Java語言合在一起構(gòu)造出來的。而我們的迷你語言則完全內(nèi)嵌在Lisp中, 只消幾分鐘就做出來了。
我們已經(jīng)說過了DSL的好處, 這也就是Ant用XML而不直接用Java的原因。如果使用Lisp,我們可以任意創(chuàng)建DSL, 只要我們需要。我們可以創(chuàng)建用于網(wǎng)站程序的DSL, 可以寫多用戶游戲, 做固定收益貿(mào)易(fixed income trade), 解決蛋白質(zhì)折疊問題, 處理事務(wù)問題, 等等。我們可以把這些疊放在一起, 造出一個語言, 專門解決基于網(wǎng)絡(luò)的貿(mào)易程序, 既有網(wǎng)絡(luò)語言的優(yōu)勢, 又有貿(mào)易語言的好處。每天我們都會收獲這種方法帶給我們的益處, 遠遠超過Ant所能給予我們的。
用DSL解決問題, 做出的程序精簡, 易于維護, 富有彈性。在Java里面, 我們可以用類來處理問題。這兩種方法的差別在于, Lisp使我們達到了一個更高層次的抽象, 我們不再受語言解析器本身的限制, 比較一下用Java庫直接寫的構(gòu)造腳本和用Ant寫的構(gòu)造腳本其間的差別。同樣的, 比較一下你以前所做的工作, 你就會明白Lisp帶來的好處。
接下來
學(xué) 習(xí)Lisp就像戰(zhàn)爭中爭奪山頭。盡管在電腦科學(xué)領(lǐng)域, Lisp已經(jīng)算是一門古老的語言, 直到現(xiàn)在仍然很少有人真的明白該怎樣給初學(xué)者講授Lisp。盡管Lisp老手們盡了很大努力,今天新手學(xué)習(xí)Lisp仍然是困難重重。好在現(xiàn)在事情正在發(fā)生 變化, Lisp的資源正在迅速增加, 隨著時間推移, Lisp將會越來越受關(guān)注。
Lisp使人超越平庸, 走到前沿。學(xué)會Lisp意味著你能找到更好的工作, 因為聰明的雇主會被你與眾不同的洞察力所打動。學(xué)會Lisp也可能意味著明天你可能會被解雇, 因為你總是強調(diào), 如果公司所有軟件都用Lisp寫, 公司將會如何卓越, 而這些話你的同事會聽煩的。Lisp值得努力學(xué)習(xí)嗎? 那些已經(jīng)學(xué)會Lisp的人都說值得, 當(dāng)然, 這取決于你的判斷。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的着墨中文lisp登入_Lisp的本质 - climbdream的个人空间 - OSCHINA - 中文开源技术交流社区...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 华为手机微信怎么双开账号 同时登陆两个账
- 下一篇: 古茗公布2023年战略目标:总门店数要破