今天小編給大家分享一下Lisp實(shí)例分析的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。
創(chuàng)新互聯(lián)建站網(wǎng)站建設(shè)提供從項(xiàng)目策劃、軟件開(kāi)發(fā),軟件安全維護(hù)、網(wǎng)站優(yōu)化(SEO)、網(wǎng)站分析、效果評(píng)估等整套的建站服務(wù),主營(yíng)業(yè)務(wù)為網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì),App定制開(kāi)發(fā)以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專(zhuān)業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。創(chuàng)新互聯(lián)建站深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
Lisp有豐富的內(nèi)置數(shù)據(jù)類(lèi)型, 其中的整數(shù)和字符串和其他語(yǔ)言沒(méi)什么分別。像71或者”hello”這樣的值, 含義也和C++或者Java這樣的語(yǔ)言大體相同。真正有意思的三種類(lèi)型是符號(hào)(symbol), 表和函數(shù)。這一章的剩余部分, 我都會(huì)用來(lái)介紹這幾種類(lèi)型, 還要介紹Lisp環(huán)境是怎樣編譯和運(yùn)行源碼的。這個(gè)過(guò)程用Lisp的術(shù)語(yǔ)來(lái)說(shuō)通常叫做求值。通讀這一節(jié)內(nèi)容, 對(duì)于透徹理解元編程的真正潛力, 以及代碼和數(shù)據(jù)的同一性, 和面向領(lǐng)域語(yǔ)言的觀念, 都極其重要。萬(wàn)勿等閑視之。我會(huì)盡量講得生動(dòng)有趣一些, 也希望你能獲得一些啟發(fā)。那好, 我們先講符號(hào)。
大體上, 符號(hào)相當(dāng)于C++或Java語(yǔ)言中的標(biāo)志符, 它的名字可以用來(lái)訪問(wèn)變量值(例如currentTime, arrayCount, n, 等等), 差別在于, Lisp中的符號(hào)更加基本。在C++或Java里面, 變量名只能用字母和下劃線的組合, 而Lisp的符號(hào)則非常有包容性, 比如, 加號(hào)(+)就是一個(gè)合法的符號(hào), 其他的像-, =, hello-world, *等等都可以是符號(hào)名。符號(hào)名的命名規(guī)則可以在網(wǎng)上查到。你可以給這些符號(hào)任意賦值, 我們這里先用偽碼來(lái)說(shuō)明這一點(diǎn)。假定函數(shù)set是給變量賦值(就像等號(hào)=在C++和Java里的作用), 下面是我們的例子:
set
(test,
5
)
/
/
符號(hào)test的值為
5
set
(
=
,
5
)
/
/
符號(hào)
=
的值為
5
set
(test,
"hello"
)
/
/
符號(hào)test的值為字符串
"hello"
set
(test,
=
)
/
/
此時(shí)符號(hào)
=
的值為
5
, 所以test的也為
5
set
(
*
,
"hello"
)
/
/
符號(hào)
*
的值為
"hello"
好像有什么不對(duì)的地方? 假定我們對(duì)*賦給整數(shù)或者字符串值, 那做乘法時(shí)怎么辦? 不管怎么說(shuō), *總是乘法呀? 答案簡(jiǎn)單極了。Lisp中函數(shù)的角色十分特殊, 函數(shù)也是一種數(shù)據(jù)類(lèi)型, 就像整數(shù)和字符串一樣, 因此可以把它賦值給符號(hào)。乘法函數(shù)Lisp的內(nèi)置函數(shù), 默認(rèn)賦給*, 你可以把其他函數(shù)賦值給*, 那樣*就不代表乘法了。你也可以把這函數(shù)的值存到另外的變量里。我們?cè)儆脗未a來(lái)說(shuō)明一下:
3
,
4
)
/
/
3
乘
4
, 結(jié)果是
12
set
(temp,
*
)
/
/
把
*
的值, 也就是乘法函數(shù), 賦值給temp
set
(
*
,
3
)
/
/
把
3
賦予
*
*
(
3
,
4
)
/
/
錯(cuò)誤的表達(dá)式,
*
不再是乘法, 而是數(shù)值
3
temp(
3
,
4
)
/
/
temp是乘法函數(shù), 所以此表達(dá)式的值為
3
乘
4
等于
12
set
(
*
, temp)
/
/
再次把乘法函數(shù)賦予
*
*
(
3
,
4
)
/
/
3
乘
4
等于
12
再古怪一點(diǎn), 把減號(hào)的值賦給加號(hào):
set
(
+
,
-
)
/
/
減號(hào)(
-
)是內(nèi)置的減法函數(shù)
+
(
5
,
4
)
/
/
加號(hào)(
+
)現(xiàn)在是代表減法函數(shù), 結(jié)果是
5
減
4
等于
1
這只是舉例子, 我還沒(méi)有詳細(xì)講函數(shù)。Lisp中的函數(shù)是一種數(shù)據(jù)類(lèi)型, 和整數(shù), 字符串,符號(hào)等等一樣。一個(gè)函數(shù)并不必然有一個(gè)名字, 這和C++或者Java語(yǔ)言的情形很不相同。在這里函數(shù)自己代表自己。事實(shí)上它是一個(gè)指向代碼塊的指針, 附帶有一些其他信息(例如一組參數(shù)變量)。只有在把函數(shù)賦予其他符號(hào)時(shí), 它才具有了名字, 就像把一個(gè)數(shù)值或字符串賦予變量一樣的道理。你可以用一個(gè)內(nèi)置的專(zhuān)門(mén)用于創(chuàng)建函數(shù)的函數(shù)來(lái)創(chuàng)建函數(shù),然后把它賦值給符號(hào)fn, 用偽碼來(lái)表示就是:
fn [a]
{
return
*(a,
2
);
}
這段代碼返回一個(gè)具有一個(gè)參數(shù)的函數(shù), 函數(shù)的功能是計(jì)算參數(shù)乘2的結(jié)果。這個(gè)函數(shù)還沒(méi)有名字, 你可以把此函數(shù)賦值給別的符號(hào):
set
(times
-
two, fn [a] {
return
*
(a,
2
)})
我們現(xiàn)在可以這樣調(diào)用這個(gè)函數(shù):
time-two(
5
)
// 返回10
我們先跳過(guò)符號(hào)和函數(shù), 講一講表。什么是表? 你也許已經(jīng)聽(tīng)過(guò)好多相關(guān)的說(shuō)法。表, 一言以蔽之, 就是把類(lèi)似XML那樣的數(shù)據(jù)塊, 用s表達(dá)式來(lái)表示。表用一對(duì)括號(hào)括住, 表中元素以空格分隔, 表可以嵌套。例如(這回我們用真正的Lisp語(yǔ)法, 注意用分號(hào)表示注釋):
() ; 空表
(
1
) ; 含一個(gè)元素的表
(
1
"test"
) ; 兩元素表, 一個(gè)元素是整數(shù)
1
, 另一個(gè)是字符串
(test
"hello"
) ; 兩元素表, 一個(gè)元素是符號(hào), 另一個(gè)是字符串
(test (
1
2
)
"hello"
) ; 三元素表, 一個(gè)符號(hào)test, 一個(gè)含有兩個(gè)元素
1
和
2
的
; 表, 最后一個(gè)元素是字符串
當(dāng)Lisp系統(tǒng)遇到這樣的表時(shí), 它所做的, 和Ant處理XML數(shù)據(jù)所做的, 非常相似, 那就是試圖執(zhí)行它們。其實(shí), Lisp源碼就是特定的一種表, 好比Ant源碼是一種特定的XML一樣。Lisp執(zhí)行表的順序是這樣的, 表的第一個(gè)元素當(dāng)作函數(shù), 其他元素當(dāng)作函數(shù)的參數(shù)。如果其中某個(gè)參數(shù)也是表, 那就按照同樣的原則對(duì)這個(gè)表求值, 結(jié)果再傳遞給最初的函數(shù)作為參數(shù)。這就是基本原則。我們看一下真正的代碼:
(
*
3
4
) ; 相當(dāng)于前面列舉過(guò)的偽碼
*
(
3
,
4
), 即計(jì)算
3
乘
4
(times
-
two
5
) ; 返回
10
, times
-
two按照前面的定義是求參數(shù)的
2
倍
(
3
4
) ; 錯(cuò)誤,
3
不是函數(shù)
(time
-
two) ; 錯(cuò)誤, times
-
two要求一個(gè)參數(shù)
(times
-
two
3
4
) ; 錯(cuò)誤, times
-
two只要求一個(gè)參數(shù)
(
set
+
-
) ; 把減法函數(shù)賦予符號(hào)
+
(
+
5
4
) ; 依據(jù)上一句的結(jié)果, 此時(shí)
+
表示減法, 所以返回
1
(
*
3
(
+
2
2
)) ;
2
+
2
的結(jié)果是
4
, 再乘
3
, 結(jié)果是
12
上述的例子中, 所有的表都是當(dāng)作代碼來(lái)處理的。怎樣把表當(dāng)作數(shù)據(jù)來(lái)處理呢? 同樣的,設(shè)想一下, Ant是把XML數(shù)據(jù)當(dāng)作自己的參數(shù)。在Lisp中, 我們給表加一個(gè)前綴’來(lái)表示數(shù)據(jù)。
(
set
test '(
1
2
)) ; test的值為兩元素表
(
set
test (
1
2
)) ; 錯(cuò)誤,
1
不是函數(shù)
(
set
test '(
*
3
4
)) ; test的值是三元素表, 三個(gè)元素分別是
*
,
3
,
4
我們可以用一個(gè)內(nèi)置的函數(shù)head來(lái)返回表的第一個(gè)元素, tail函數(shù)來(lái)返回剩余元素組成的表。
(head '(
*
3
4
)) ; 返回符號(hào)
*
(tail '(
*
3
4
)) ; 返回表(
3
4
)
(head (tal '(
*
3
4
))) ; 返回
3
(head test) ; 返回
*
你可以把Lisp的內(nèi)置函數(shù)想像成Ant的任務(wù)。差別在于, 我們不用在另外的語(yǔ)言中擴(kuò)展Lisp(雖然完全可以做得到), 我們可以用Lisp自己來(lái)擴(kuò)展自己, 就像上面舉的times-two函數(shù)的例子。Lisp的內(nèi)置函數(shù)集十分精簡(jiǎn), 只包含了十分必要的部分。剩下的函數(shù)都是作為標(biāo)準(zhǔn)庫(kù)來(lái)實(shí)現(xiàn)的。
Lisp宏
我們已經(jīng)看到, 元編程在一個(gè)類(lèi)似jsp的模板引擎方面的應(yīng)用。我們通過(guò)簡(jiǎn)單的字符串處理來(lái)生成代碼。但是我們可以做的更好。我們先提一個(gè)問(wèn)題, 怎樣寫(xiě)一個(gè)工具, 通過(guò)查找目錄結(jié)構(gòu)中的源文件來(lái)自動(dòng)生成Ant腳本。
用字符串處理的方式生成Ant腳本是一種簡(jiǎn)單的方式。當(dāng)然, 還有一種更加抽象, 表達(dá)能力更強(qiáng), 擴(kuò)展性更好的方式, 就是利用XML庫(kù)在內(nèi)存中直接生成XML節(jié)點(diǎn), 這樣的話內(nèi)存中的節(jié)點(diǎn)就可以自動(dòng)序列化成為字符串。不僅如此, 我們的工具還可以分析這些節(jié)點(diǎn), 對(duì)已有的XML文件做變換。通過(guò)直接處理XML節(jié)點(diǎn)。我們可以超越字符串處理, 使用更高層次的概念, 因此我們的工作就會(huì)做的更快更好。
我們當(dāng)然可以直接用Ant自身來(lái)處理XML變換和制作代碼生成工具?;蛘呶覀円部梢杂肔isp來(lái)做這項(xiàng)工作。正像我們以前所知的, 表是Lisp內(nèi)置的數(shù)據(jù)結(jié)構(gòu), Lisp含有大量的工具來(lái)快速有效的操作表(head和tail是最簡(jiǎn)單的兩個(gè))。而且, Lisp沒(méi)有語(yǔ)義約束, 你可以構(gòu)造任何數(shù)據(jù)結(jié)構(gòu), 只要你原意。
Lisp通過(guò)宏(macro)來(lái)做元編程。我們寫(xiě)一組宏來(lái)把任務(wù)列表(to-do list)轉(zhuǎn)換為專(zhuān)用領(lǐng)域語(yǔ)言。
回想一下上面to-do list的例子, 其XML的數(shù)據(jù)格式是這樣的:
<
todo
name
=
"housework"
>
<
item
priority
=
"high"
>Clean the hose
item
>
<
item
priority
=
"medium"
>Wash the dishes
item
>
<
item
priority
=
"medium"
>Buy more soap
item
>
todo
>
相應(yīng)的s表達(dá)式是這樣的:
(todo
"housework"
(item (priority high)
"Clean the house"
)
(item (priority medium)
"Wash the dishes"
)
(item (priority medium)
"Buy more soap"
))
假設(shè)我們要寫(xiě)一個(gè)任務(wù)表的管理程序, 把任務(wù)表數(shù)據(jù)存到一組文件里, 當(dāng)程序啟動(dòng)時(shí), 從文件讀取這些數(shù)據(jù)并顯示給用戶。在別的語(yǔ)言里(比如說(shuō)Java), 這個(gè)任務(wù)該怎么做? 我們會(huì)解析XML文件, 從中得出任務(wù)表數(shù)據(jù), 然后寫(xiě)代碼遍歷XML樹(shù), 再轉(zhuǎn)換為Java的數(shù)據(jù)結(jié)構(gòu)(老實(shí)講, 在Java里解析XML真不是件輕松的事情), 最后再把數(shù)據(jù)展示給用戶。現(xiàn)在如果用Lisp, 該怎么做?
假定要用同樣思路的化, 我們大概會(huì)用Lisp庫(kù)來(lái)解析XML。XML對(duì)我們來(lái)說(shuō)就是一個(gè)Lisp的表(s表達(dá)式), 我們可以遍歷這個(gè)表, 然后把相關(guān)數(shù)據(jù)提交給用戶。可是, 既然我們用Lisp, 就根本沒(méi)有必要再用XML格式保存數(shù)據(jù), 直接用s表達(dá)式就好了, 這樣就沒(méi)有必要做轉(zhuǎn)換了。我們也用不著專(zhuān)門(mén)的解析庫(kù), Lisp可以直接在內(nèi)存里處理s表達(dá)式。注意, Lisp編譯器和.net編譯器一樣, 對(duì)Lisp程序來(lái)說(shuō), 在運(yùn)行時(shí)總是隨時(shí)可用的。
但是還有更好的辦法。我們甚至不用寫(xiě)表達(dá)式來(lái)存儲(chǔ)數(shù)據(jù), 我們可以寫(xiě)宏, 把數(shù)據(jù)當(dāng)作代碼來(lái)處理。那該怎么做呢? 真的簡(jiǎn)單?;叵胍幌? Lisp的函數(shù)調(diào)用格式:
(function
-
name arg1 arg2 arg3)
其中每個(gè)參數(shù)都是s表達(dá)式, 求值以后, 傳遞給函數(shù)。如果我們用(+ 4 5)來(lái)代替arg1,那么, 程序會(huì)先求出結(jié)果, 就是9, 然后把9傳遞給函數(shù)。宏的工作方式和函數(shù)類(lèi)似。主要的差別是, 宏的參數(shù)在代入時(shí)不求值。
(macro
-
name (
+
4
5
))
這里, (+ 4 5)作為一個(gè)表傳遞給宏, 然后宏就可以任意處理這個(gè)表, 當(dāng)然也可以對(duì)它求值。宏的返回值是一個(gè)表, 然后有程序作為代碼來(lái)執(zhí)行。宏所占的位置, 就被替換為這個(gè)結(jié)果代碼。我們可以定義一個(gè)宏把數(shù)據(jù)替換為任意代碼, 比方說(shuō), 替換為顯示數(shù)據(jù)給用戶的代碼。
這和元編程, 以及我們要做的任務(wù)表程序有什么關(guān)系呢? 實(shí)際上, 編譯器會(huì)替我們工作,調(diào)用相應(yīng)的宏。我們所要做的, 僅僅是創(chuàng)建一個(gè)把數(shù)據(jù)轉(zhuǎn)換為適當(dāng)代碼的宏。
例如, 上面曾經(jīng)將過(guò)的C的求三次方的宏, 用Lisp來(lái)寫(xiě)是這樣子:
(defmacro triple (x)
`(
+
~x ~x ~x))
(譯注: 在Common Lisp中, 此處的單引號(hào)應(yīng)當(dāng)是反單引號(hào), 意思是對(duì)表不求值, 但可以對(duì)表中某元素求值, 記號(hào)~表示對(duì)元素x求值, 這個(gè)求值記號(hào)在Common Lisp中應(yīng)當(dāng)是逗號(hào)。反單引號(hào)和單引號(hào)的區(qū)別是, 單引號(hào)標(biāo)識(shí)的表, 其中的元素都不求值。這里作者所用的記號(hào)是自己發(fā)明的一種Lisp方言Blaise, 和common lisp略有不同, 事實(shí)上, 發(fā)明方言是lisp高手獨(dú)有的樂(lè)趣, 很多狂熱分子都熱衷這樣做。比如Paul Graham就發(fā)明了ARC, 許多記號(hào)比傳統(tǒng)的Lisp簡(jiǎn)潔得多, 顯得比較現(xiàn)代)
單引號(hào)的用處是禁止對(duì)表求值。每次程序中出現(xiàn)triple的時(shí)候,
(triple
4
)
都會(huì)被替換成:
(
+
4
4
4
)
我們可以為任務(wù)表程序?qū)懸粋€(gè)宏, 把任務(wù)數(shù)據(jù)轉(zhuǎn)換為可執(zhí)行碼, 然后執(zhí)行。假定我們的輸出是在控制臺(tái):
(defmacro item (priority note)
`(block
(
stdout tab
"Prority: "
~(head (tail priority)) endl)
(
stdout tab
"Note: "
~note endl endl)))
我們創(chuàng)造了一個(gè)非常小的有限的語(yǔ)言來(lái)管理嵌在Lisp中的任務(wù)表。這個(gè)語(yǔ)言只用來(lái)解決特定領(lǐng)域的問(wèn)題, 通常稱(chēng)之為DSLs(特定領(lǐng)域語(yǔ)言, 或?qū)S妙I(lǐng)域語(yǔ)言)。
以上就是“Lisp實(shí)例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。