溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Lisp實(shí)例分析

發(fā)布時(shí)間:2022-03-19 10:18:40 來(lái)源:億速云 閱讀:208 作者:iii 欄目:云計(jì)算

今天小編給大家分享一下Lisp實(shí)例分析的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。

Lisp有豐富的內(nèi)置數(shù)據(jù)類型, 其中的整數(shù)和字符串和其他語(yǔ)言沒什么分別。像71或者”hello”這樣的值, 含義也和C++或者Java這樣的語(yǔ)言大體相同。真正有意思的三種類型是符號(hào)(symbol), 表和函數(shù)。這一章的剩余部分, 我都會(huì)用來(lái)介紹這幾種類型, 還要介紹Lisp環(huán)境是怎樣編譯和運(yùn)行源碼的。這個(gè)過程用Lisp的術(shù)語(yǔ)來(lái)說通常叫做求值。通讀這一節(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)訪問變量值(例如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)說明這一點(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í)怎么辦? 不管怎么說, *總是乘法呀? 答案簡(jiǎn)單極了。Lisp中函數(shù)的角色十分特殊, 函數(shù)也是一種數(shù)據(jù)類型, 就像整數(shù)和字符串一樣, 因此可以把它賦值給符號(hào)。乘法函數(shù)Lisp的內(nèi)置函數(shù), 默認(rèn)賦給*, 你可以把其他函數(shù)賦值給*, 那樣*就不代表乘法了。你也可以把這函數(shù)的值存到另外的變量里。我們?cè)儆脗未a來(lái)說明一下:

3,4)          // 34, 結(jié)果是12

set(temp, *)    // *的值, 也就是乘法函數(shù), 賦值給temp

set(*3)       // 3賦予*

*(3,4)          // 錯(cuò)誤的表達(dá)式, *不再是乘法, 而是數(shù)值3

temp(3,4)       // temp是乘法函數(shù), 所以此表達(dá)式的值為34等于12

set(*, temp)    // 再次把乘法函數(shù)賦予*

*(3,4)          // 34等于12

再古怪一點(diǎn), 把減號(hào)的值賦給加號(hào):

set(+-)       // 減號(hào)(-)是內(nèi)置的減法函數(shù)

+(54)         // 加號(hào)(+)現(xiàn)在是代表減法函數(shù), 結(jié)果是54等于1

這只是舉例子, 我還沒有詳細(xì)講函數(shù)。Lisp中的函數(shù)是一種數(shù)據(jù)類型, 和整數(shù), 字符串,符號(hào)等等一樣。一個(gè)函數(shù)并不必然有一個(gè)名字, 這和C++或者Java語(yǔ)言的情形很不相同。在這里函數(shù)自己代表自己。事實(shí)上它是一個(gè)指向代碼塊的指針, 附帶有一些其他信息(例如一組參數(shù)變量)。只有在把函數(shù)賦予其他符號(hào)時(shí), 它才具有了名字, 就像把一個(gè)數(shù)值或字符串賦予變量一樣的道理。你可以用一個(gè)內(nèi)置的專門用于創(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ù)還沒有名字, 你可以把此函數(shù)賦值給別的符號(hào):

set(times-two, fn [a] {return *(a, 2)})

我們現(xiàn)在可以這樣調(diào)用這個(gè)函數(shù):

time-two(5)         // 返回10

我們先跳過符號(hào)和函數(shù), 講一講表。什么是表? 你也許已經(jīng)聽過好多相關(guān)的說法。表, 一言以蔽之, 就是把類似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è)元素12

; 表, 最后一個(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)于前面列舉過的偽碼*(3,4), 即計(jì)算34

(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è)元素分別是*34

我們可以用一個(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è)類似jsp的模板引擎方面的應(yīng)用。我們通過簡(jiǎn)單的字符串處理來(lái)生成代碼。但是我們可以做的更好。我們先提一個(gè)問題, 怎樣寫一個(gè)工具, 通過查找目錄結(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文件做變換。通過直接處理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沒有語(yǔ)義約束, 你可以構(gòu)造任何數(shù)據(jù)結(jié)構(gòu), 只要你原意。

Lisp通過宏(macro)來(lái)做元編程。我們寫一組宏來(lái)把任務(wù)列表(to-do list)轉(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è)我們要寫一個(gè)任務(wù)表的管理程序, 把任務(wù)表數(shù)據(jù)存到一組文件里, 當(dāng)程序啟動(dòng)時(shí), 從文件讀取這些數(shù)據(jù)并顯示給用戶。在別的語(yǔ)言里(比如說Java), 這個(gè)任務(wù)該怎么做? 我們會(huì)解析XML文件, 從中得出任務(wù)表數(shù)據(jù), 然后寫代碼遍歷XML樹, 再轉(zhuǎn)換為Java的數(shù)據(jù)結(jié)構(gòu)(老實(shí)講, 在Java里解析XML真不是件輕松的事情), 最后再把數(shù)據(jù)展示給用戶?,F(xiàn)在如果用Lisp, 該怎么做?

假定要用同樣思路的化, 我們大概會(huì)用Lisp庫(kù)來(lái)解析XML。XML對(duì)我們來(lái)說就是一個(gè)Lisp的表(s表達(dá)式), 我們可以遍歷這個(gè)表, 然后把相關(guān)數(shù)據(jù)提交給用戶。可是, 既然我們用Lisp, 就根本沒有必要再用XML格式保存數(shù)據(jù), 直接用s表達(dá)式就好了, 這樣就沒有必要做轉(zhuǎn)換了。我們也用不著專門的解析庫(kù), Lisp可以直接在內(nèi)存里處理s表達(dá)式。注意, Lisp編譯器和.net編譯器一樣, 對(duì)Lisp程序來(lái)說, 在運(yùn)行時(shí)總是隨時(shí)可用的。

但是還有更好的辦法。我們甚至不用寫表達(dá)式來(lái)存儲(chǔ)數(shù)據(jù), 我們可以寫宏, 把數(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ù)類似。主要的差別是, 宏的參數(shù)在代入時(shí)不求值。

(macro-name (+ 4 5))

這里, (+ 4 5)作為一個(gè)表傳遞給宏, 然后宏就可以任意處理這個(gè)表, 當(dāng)然也可以對(duì)它求值。宏的返回值是一個(gè)表, 然后有程序作為代碼來(lái)執(zhí)行。宏所占的位置, 就被替換為這個(gè)結(jié)果代碼。我們可以定義一個(gè)宏把數(shù)據(jù)替換為任意代碼, 比方說, 替換為顯示數(shù)據(jù)給用戶的代碼。

這和元編程, 以及我們要做的任務(wù)表程序有什么關(guān)系呢? 實(shí)際上, 編譯器會(huì)替我們工作,調(diào)用相應(yīng)的宏。我們所要做的, 僅僅是創(chuàng)建一個(gè)把數(shù)據(jù)轉(zhuǎn)換為適當(dāng)代碼的宏。

例如, 上面曾經(jīng)將過的C的求三次方的宏, 用Lisp來(lái)寫是這樣子:

(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ú)有的樂趣, 很多狂熱分子都熱衷這樣做。比如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

(print stdout tab "Prority: " ~(head (tail priority)) endl)

(print stdout tab "Note: " ~note endl endl)))

我們創(chuàng)造了一個(gè)非常小的有限的語(yǔ)言來(lái)管理嵌在Lisp中的任務(wù)表。這個(gè)語(yǔ)言只用來(lái)解決特定領(lǐng)域的問題, 通常稱之為DSLs(特定領(lǐng)域語(yǔ)言, 或?qū)S妙I(lǐng)域語(yǔ)言)。

以上就是“Lisp實(shí)例分析”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI