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