您好,登錄后才能下訂單哦!
這篇“Lisp的本質是什么”文章的知識點大部分人都不太理解,所以小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Lisp的本質是什么”文章吧。
重新審視XML
千里之行始于足下。讓我們的第一步從XML開始。可是XML已經說得更多的了, 還能有什么新意思可說呢? 有的。XML自身雖然談談不上有趣, 但是XML和Lisp的關系卻相當有趣。XML和Lisp的概念有著驚人的相似之處。XML是我們通向理解Lisp的橋梁。好吧, 我們且把XML當作活馬醫。讓我們拿好手杖, 對XML的無人涉及的荒原地帶作一番探險。我們要從一個全新的視角來考察這個題目。
表面上看, XML是一種標準化語法, 它以適合人閱讀的格式來表達任意的層次化數據(hirearchical data)。象任務表(to-do list), 網頁, 病歷, 汽車保險單, 配置文件等等, 都是XML用武的地方。比如我們拿任務表做例子:
<
todo
name
=
"housework"
>
<
item
priority
=
"high"
>Clean the house.</
item
>
<
item
priority
=
"medium"
>Wash the dishes.</
item
>
<
item
priority
=
"medium"
>Buy more soap.</
item
>
</
todo
>
解析這段數據時會發生什么情況? 解析之后的數據在內存中怎樣表示? 顯然, 用樹來表示這種層次化數據是很恰當的。說到底, XML這種比較容易閱讀的數據格式, 就是樹型結構數據經過序列化之后的結果。任何可以用樹來表示的數據, 同樣可以用XML來表示, 反之亦然。希望你能懂得這一點, 這對下面的內容極其重要。
再進一步。還有什么類型的數據也常用樹來表示? 無疑列表(list)也是一種。上過編譯課吧? 還模模糊糊記得一點吧? 源代碼在解析之后也是用樹結構來存放的, 任何編譯程序都會把源代碼解析成一棵抽象語法樹, 這樣的表示法很恰當, 因為源代碼就是層次結構的:函數包含參數和代碼塊, 代碼快包含表達式和語句, 語句包含變量和運算符等等。
我們已經知道, 任何樹結構都可以輕而易舉的寫成XML, 而任何代碼都會解析成樹, 因此,任何代碼都可以轉換成XML, 對不對? 我舉個例子, 請看下面的函數:
int
add(
int
arg1,
int
arg2)
{
return
arg1+arg2;
}
能把這個函數變成對等的XML格式嗎? 當然可以。我們可以用很多種方式做到, 下面是其中的一種, 十分簡單:
<
define-function
return-type
=
"int"
name
=
"add"
>
<
arguments
>
<
argument
type
=
"int"
>arg1</
argument
>
<
argument
type
=
"int"
>arg2</
argument
>
</
arguments
>
<
body
>
<
return
>
<
add
value1
=
"arg1"
value2
=
"arg2"
/>
</
return
>
</
body
>
</
define
>
這個例子非常簡單, 用哪種語言來做都不會有太大問題。我們可以把任何程序碼轉成XML,也可以把XML轉回到原來的程序碼。我們可以寫一個轉換器, 把Java代碼轉成XML, 另一個轉換器把XML轉回到Java。一樣的道理, 這種手段也可以用來對付C++(這樣做跟發瘋差不多么。可是的確有人在做, 看看GCC-XML(http://www.gccxml.org)就知道了)。進一步說,凡是有相同語言特性而語法不同的語言, 都可以把XML當作中介來互相轉換代碼。實際上幾乎所有的主流語言都在一定程度上滿足這個條件。我們可以把XML作為一種中間表示法,在兩種語言之間互相譯碼。比方說, 我們可以用Java2XML把Java代碼轉換成XML, 然后用XML2CPP再把XML轉換成C++代碼, 運氣好的話, 就是說, 如果我們小心避免使用那些C++不具備的Java特性的話, 我們可以得到完好的C++程序。這辦法怎么樣, 漂亮吧?
這一切充分說明, 我們可以把XML作為源代碼的通用存儲方式, 其實我們能夠產生一整套使用統一語法的程序語言, 也能寫出轉換器, 把已有代碼轉換成XML格式。如果真的采納這種辦法, 各種語言的編譯器就用不著自己寫語法解析了, 它們可以直接用XML的語法解析來直接生成抽象語法樹。
說到這里你該問了, 我們研究了這半天XML, 這和Lisp有什么關系呢? 畢竟XML出來之時,Lisp早已經問世三十年了。這里我可以保證, 你馬上就會明白。不過在繼續解釋之前, 我們先做一個小小的思維練習。看一下上面這個XML版本的add函數例子, 你怎樣給它分類,是代碼還是數據? 不用太多考慮都能明白, 把它分到哪一類都講得通。它是XML, 它是標準格式的數據。我們也知道, 它可以通過內存中的樹結構來生成(GCC-XML做的就是這個事情)。它保存在不可執行的文件中。我們可以把它解析成樹節點, 然后做任意的轉換。顯而易見, 它是數據。不過且慢, 雖然它語法有點陌生, 可它又確確實實是一個add函數,對吧? 一旦經過解析, 它就可以拿給編譯器編譯執行。我們可以輕而易舉寫出這個XML代碼解釋器, 并且直接運行它。或者我們也可以把它譯成Java或C++代碼, 然后再編譯運行。所以說, 它也是代碼。
我們說到那里了? 不錯, 我們已經發現了一個有趣的關鍵之點。過去被認為很難解的概念已經非常直觀非常簡單的顯現出來。代碼也是數據, 并且從來都是如此。這聽起來瘋瘋癲癲的, 實際上卻是必然之事。我許諾過會以一種全新的方式來解釋Lisp, 我要重申我的許諾。但是我們此刻還沒有到預定的地方, 所以還是先繼續上邊的討論。
剛才我說過, 我們可以非常簡單地實現XML版的add函數解釋器, 這聽起來好像不過是說說而已。誰真的會動手做一下呢? 未必有多少人會認真對待這件事。隨便說說, 并不打算真的去做, 這樣的事情你在生活中恐怕也遇到吧。你明白我這樣說的意思吧, 我說的有沒有打動你? 有哇, 那好, 我們繼續。
重新審視Ant
我們現在已經來到了月亮背光的那一面, 先別忙著離開。再探索一下, 看看我們還能發現什么東西。閉上眼睛, 想一想2000年冬天的那個雨夜, 一個名叫James Duncan Davidson的杰出的程序員正在研究Tomcat的servlet容器。那時, 他正小心地保存好剛修改過的文件, 然后執行make。結果冒出了一大堆錯誤, 顯然有什么東西搞錯了。經過仔細檢查, 他想, 難道是因為tab前面加了個空格而導致命令不能執行嗎? 確實如此。老是這樣, 他真的受夠了。烏云背后的月亮給了他啟示, 他創建了一個新的Java項目, 然后寫了一個簡單但是十分有用的工具, 這個工具巧妙地利用了Java屬性文件中的信息來構造工程, 現在James可以寫makefile的替代品, 它能起到相同的作用, 而形式更加優美, 也不用擔心有makefile那樣可恨的空格問題。這個工具能夠自動解釋屬性文件, 然后采取正確的動作來編譯工程。真是簡單而優美。
(作者注: 我不認識James, James也不認識我, 這個故事是根據網上關于Ant歷史的帖子虛構的)
使用Ant構造Tomcat之后幾個月, 他越來越感到Java的屬性文件不足以表達復雜的構造指令。文件需要檢出, 拷貝, 編譯, 發到另外一臺機器, 進行單元測試。要是出錯, 就發郵件給相關人員, 要是成功, 就繼續在盡可能高層的卷(volumn)上執行構造。追蹤到最后,卷要回復到最初的水平上。確實, Java的屬性文件不夠用了, James需要更有彈性的解決方案。他不想自己寫解析器(因為他更希望有一個具有工業標準的方案)。XML看起來是個不錯的選擇。他花了幾天工夫把Ant移植到XML,于是,一件偉大的工具誕生了。
Ant是怎樣工作的?原理非常簡單。Ant把包含有構造命令的XML文件(算代碼還是算數據,你自己想吧),交給一個Java程序來解析每一個元素,實際情況比我說的還要簡單得多。一個簡單的XML指令會導致具有相同名字的Java類裝入,并執行其代碼。
<copy todir=
"../new/dir"
>
<fileset
dir
=
"src_dir"
/>
<
/copy
>
這段文字的含義是把源目錄復制到目標目錄,Ant會找到一個”copy”任務(實際上就是一個Java類), 通過調用Java的方法來設置適當參數(todir和fileset),然后執行這個任務。Ant帶有一組核心類, 可以由用戶任意擴展, 只要遵守若干約定就可以。Ant找到這些類,每當遇到XML元素有同樣的名字, 就執行相應的代碼。過程非常簡單。Ant做到了我們前面所說的東西: 它是一個語言解釋器, 以XML作為語法, 把XML元素轉譯為適當的Java指令。我們可以寫一個”add”任務, 然后, 當發現XML中有add描述的時候, 就執行這個add任務。由于Ant是非常流行的項目, 前面展示的策略就顯得更為明智。畢竟, 這個工具每天差不多有幾千家公司在使用。
到目前為之, 我還沒有說Ant在解析XML時所遇到困難。你也不用麻煩去它的網站上去找答案了, 不會找到有價值的東西。至少對我們這個論題來說是如此。我們還是繼續下一步討論吧。我們答案就在那里。
為什么是XML
有時候正確的決策并非完全出于深思熟慮。我不知道James選擇XML是否出于深思熟慮。也許僅僅是個下意識的決定。至少從James在Ant網站上發表的文章看起來, 他所說的理由完全是似是而非。他的主要理由是移植性和擴展性, 在Ant案例上, 我看不出這兩條有什么幫助。使用XML而不是Java代碼, 到底有什么好處? 為什么不寫一組Java類, 提供api來滿足基本任務(拷貝目錄, 編譯等等), 然后在Java里直接調用這些代碼? 這樣做仍然可以保證移植性, 擴展性也是毫無疑問的。而且語法也更為熟悉, 看著順眼。那為什么要用 XML呢? 有什么更好的理由嗎?
有的。雖然我不確定James是否確實意識到了。在語義的可構造性方面, XML的彈性是Java望塵莫及的。我不想用高深莫測的名詞來嚇唬你, 其中的道理相當簡單, 解釋起來并不費很多功夫。好, 做好預備動作, 我們馬上就要朝向頓悟的時刻做奮力一躍。
上面的那個copy的例子, 用Java代碼怎樣實現呢? 我們可以這樣做:
CopyTask copy =
new
CopyTask();
Fileset fileset =
new
Fileset();
fileset.setDir(
"src_dir"
);
copy.setToDir(
"../new/dir"
);
copy.setFileset(fileset);
copy.excute();
這個代碼看起來和XML的那個很相似, 只是稍微長一點。差別在那里? 差別在于XML構造了一個特殊的copy動詞, 如果我們硬要用Java來寫的話, 應該是這個樣子:
copy(
"../new/dir"
);
{
fileset(
"src_dir"
);
}
看到差別了嗎? 以上代碼(如果可以在Java中用的化), 是一個特殊的copy算符, 有點像for循環或者Java5中的foreach循環。如果我們有一個轉換器, 可以把XML轉換到Java, 大概就會得到上面這段事實上不可以執行的代碼。因為Java的技術規范是定死的, 我們沒有辦法在程序里改變它。我們可以增加包, 增加類, 增加方法, 但是我們沒辦法增加算符,而對于XML, 我們顯然可以任由自己增加這樣的東西。對于XML的語法樹來說, 只要原意,我們可以任意增加任何元素, 因此等于我們可以任意增加算符。如果你還不太明白的話,看下面這個例子, 加入我們要給Java引入一個unless算符:
unless(someObject.canFly())
{
someObject.transportByGround():
}
在上面的兩個例子中, 我們打算給Java語法擴展兩個算符, 成組拷貝文件算符和條件算符unless, 我們要想做到這一點, 就必須修改Java編譯器能夠接受的抽象語法樹, 顯然我們無法用Java標準的功能來實現它。但是在XML中我們可以輕而易舉地做到。我們的解析器根據 XML元素, 生成抽象語法樹, 由此生成算符, 所以, 我們可以任意引入任何算符。
對于復雜的算符來說, 這樣做的好處顯而易見。比如, 用特定的算符來做檢出源碼, 編譯文件, 單元測試, 發送郵件等任務, 想想看有多么美妙。對于特定的題目, 比如說構造軟件項目, 這些算符的使用可以大幅減低少代碼的數量。增加代碼的清晰程度和可重用性。解釋性的XML可以很容易的達到這個目標。XML是存儲層次化數據的簡單數據文件, 而在Java中, 由于層次結構是定死的(你很快就會看到, Lisp的情況與此截然不同), 我們就沒法達到上述目標。也許這正是Ant的成功之處呢。
你可以注意一下最近Java和C#的變化(尤其是C#3.0的技術規范), C#把常用的功能抽象出來, 作為算符增加到C#中。C#新增加的query算符就是一個例子。它用的還是傳統的作法:C#的設計者修改抽象語法樹, 然后增加對應的實現。如果程序員自己也能修改抽象語法樹該有多好! 那樣我們就可以構造用于特定問題的子語言(比如說就像Ant這種用于構造項目的語言), 你能想到別的例子嗎? 再思考一下這個概念。不過也不必思考太甚, 我們待會還會回到這個題目。那時候就會更加清晰。
離Lisp越來越近
我們先把算符的事情放一放, 考慮一下Ant設計局限之外的東西。我早先說過, Ant可以通過寫Java類來擴展。Ant解析器會根據名字來匹配XML元素和Java類, 一旦找到匹配, 就執行相應任務。為什么不用Ant自己來擴展Ant呢? 畢竟核心任務要包含很多傳統語言的結構(例如”if”), 如果Ant自身就能提供構造任務的能力(而不是依賴java類), 我們就可以得到更高的移植性。我們將會依賴一組核心任務(如果你原意, 也不妨把它稱作標準庫), 而不用管有沒有Java 環境了。這組核心任務可以用任何方式來實現, 而其他任務建筑在這組核心任務之上, 那樣的話, Ant就會成為通用的, 可擴展的, 基于XML的編程語言。考慮下面這種代碼的可能性:
<
task
name
=
"Test"
>
<
echo
message
=
"Hello World"
/>
</
task
>
<
Test
/>
如果XML支持”task”的創建, 上面這段代碼就會輸出”Hello World!”. 實際上, 我們可以用Java寫個”task”任務, 然后用Ant-XML來擴展它。Ant可以在簡單原語的基礎上寫出更復雜的原語, 就像其他編程語言常用的作法一樣。這也就是我們一開始提到的基于XML的編程語言。這樣做用處不大(你知道為甚么嗎?), 但是真的很酷。
再看一回我們剛才說的Task任務。祝賀你呀, 你在看Lisp代碼!!! 我說什么? 一點都不像Lisp嗎? 沒關系, 我們再給它收拾一下。
比XML更好
前面一節說過, Ant自我擴展沒什么大用, 原因在于XML很煩瑣。對于數據來說, 這個問題還不太大, 但如果代碼很煩瑣的話, 光是打字上的麻煩就足以抵消它的好處。你寫過Ant的腳本嗎? 我寫過, 當腳本達到一定復雜度的時候, XML非常讓人厭煩。想想看吧, 為了寫結束標簽, 每個詞都得打兩遍, 不發瘋算好的!
為了解決這個問題, 我們應當簡化寫法。須知, XML僅僅是一種表達層次化數據的方式。我們并不是一定要使用尖括號才能得到樹的序列化結果。我們完全可以采用其他的格式。其中的一種(剛好就是Lisp所采用的)格式, 叫做s表達式。s表達式要做的和XML一樣, 但它的好處是寫法更簡單, 簡單的寫法更適合代碼輸入。后面我會詳細講s表達式。這之前我要清理一下XML的東西。考慮一下關于拷貝文件的例子:
<
copy
toDir
=
"../new/dir"
>
<
fileset
dir
=
"src_dir"
>
</
copy
>
想想看在內存里面, 這段代碼的解析樹在內存會是什么樣子? 會有一個”copy”節點, 其下有一個 “fileset”節點, 但是屬性在哪里呢? 它怎樣表達呢? 如果你以前用過XML, 并且弄不清楚該用元素還是該用屬性, 你不用感到孤單, 別人一樣糊涂著呢。沒人真的搞得清楚。這個選擇與其說是基于技術的理由, 還不如說是閉著眼瞎摸。從概念上來講, 屬性也是一種元素, 任何屬性能做的, 元素一樣做得到。XML引入屬性的理由, 其實就是為了讓XML寫法不那么冗長。比如我們看個例子:
<
copy
>
<
toDir
>../new/dir</
toDir
>
<
fileset
>
<
dir
>src_dir</
dir
>
</
fileset
>
</
copy
>
兩下比較, 內容的信息量完全一樣, 用屬性可以減少打字數量。如果XML沒有屬性的話,光是打字就夠把人搞瘋掉。
說完了屬性的問題, 我們再來看一看s表達式。之所以繞這么個彎, 是因為s表達式沒有屬性的概念。因為s表達式非常簡練, 根本沒有必要引入屬性。我們在把XML轉換成s表達式的時候, 心里應該記住這一點。看個例子, 上面的代碼譯成s表達式是這樣的:
(copy
(todir
"../new/dir"
)
(fileset (
dir
"src_dir"
)))
仔細看看這個例子, 差別在哪里? 尖括號改成了圓括號, 每個元素原來是有一對括號標記包圍的, 現在取消了后一個(就是帶斜杠的那個)括號標記。表示元素的結束只需要一個”)”就可以了。不錯, 差別就是這些。這兩種表達方式的轉換, 非常自然, 也非常簡單。s表達式打起字來, 也省事得多。第一次看s表達式(Lisp)時, 括號很煩人是吧? 現在我們明白了背后的道理, 一下子就變得容易多了。至少, 比XML要好的多。用s表達式寫代碼, 不單是實用, 而且也很讓人愉快。s表達式具有XML的一切好處, 這些好處是我們剛剛探討過的。現在我們看看更加Lisp風格的task例子:
(task (name
"Test"
)
(echo (message
"Hellow World!"
)))
(Test)
用Lisp的行話來講, s表達式稱為表(list)。對于上面的例子, 如果我們寫的時候不加換行, 用逗號來代替空格, 那么這個表達式看起來就非常像一個元素列表, 其中又嵌套著其他標記。
(task, (name,
"test"
), (echo, (message,
"Hello World!"
)))
XML自然也可以用這樣的風格來寫。當然上面這句并不是一般意義上的元素表。它實際上是一個樹。這和XML的作用是一樣的。稱它為列表, 希望你不會感到迷惑, 因為嵌套表和樹實際上是一碼事。Lisp的字面意思就是表處理(list processing), 其實也可以稱為樹處理, 這和處理XML節點沒有什么不同。
經受這一番折磨以后, 現在我們終于相當接近Lisp了, Lisp的括號的神秘本質(就像許多Lisp狂熱分子認為的)逐漸顯現出來。現在我們繼續研究其他內容。
重新審視C語言的宏
到了這里, 對XML的討論你大概都聽累了, 我都講累了。我們先停一停, 把樹, s表達式,Ant這些東西先放一放, 我們來說說C的預處理器。一定有人問了, 我們的話題和C有什么關系? 我們已經知道了很多關于元編程的事情, 也探討過專門寫代碼的代碼。理解這問題有一定難度, 因為相關討論文章所使用的編程語言, 都是你們不熟悉的。但是如果只論概念的話, 就相對要簡單一些。我相信, 如果以C語言做例子來討論元編程, 理解起來一定會容易得多。好, 我們接著看。
一個問題是, 為什么要用代碼來寫代碼呢? 在實際的編程中, 怎樣做到這一點呢? 到底元編程是什么意思? 你大概已經聽說過這些問題的答案, 但是并不懂得其中緣由。為了揭示背后的真理, 我們來看一下一個簡單的數據庫查詢問題。這種題目我們都做過。比方說,直接在程序碼里到處寫SQL語句來修改表(table)里的數據, 寫多了就非常煩人。即便用C#3.0的LINQ, 仍然不減其痛苦。寫一個完整的SQL查詢(盡管語法很優美)來修改某人的地址, 或者查找某人的名字, 絕對是件令程序員倍感乏味的事情, 那么我們該怎樣來解決這個問題? 答案就是: 使用數據訪問層。
概念挺簡單, 其要點是把數據訪問的內容(至少是那些比較瑣碎的部分)抽象出來, 用類來映射數據庫的表, 然后用訪問對象屬性訪問器(accessor)的辦法來間接實現查詢。這樣就極大地簡化了開發工作量。我們用訪問對象的方法(或者屬性賦值, 這要視你選用的語言而定)來代替寫SQL查詢語句。凡是用過這種方法的人, 都知道這很節省時間。當然, 如果你要親自寫這樣一個抽象層, 那可是要花非常多的時間的–你要寫一組類來映射表, 把屬性訪問轉換為SQL查詢, 這個活相當耗費精力。用手工來做顯然是很不明智的。但是一旦你有了方案和模板, 實際上就沒有多少東西需要思考的。你只需要按照同樣的模板一次又一次重復編寫相似代碼就可以了。事實上很多人已經發現了更好的方法, 有一些工具可以幫助你連接數據庫, 抓取數據庫結構定義(schema), 按照預定義的或者用戶定制的模板來自動編寫代碼。
如果你用過這種工具, 你肯定會對它的神奇效果深為折服。往往只需要鼠標點擊數次, 就可以連接到數據庫, 產生數據訪問源碼, 然后把文件加入到你的工程里面, 十幾分鐘的工作, 按照往常手工方式來作的話, 也許需要數百個小時人工(man-hours)才能完成。可是,如果你的數據庫結構定義后來改變了怎么辦? 那樣的話, 你只需把這個過程重復一遍就可以了。甚至有一些工具能自動完成這項變動工作。你只要把它作為工程構造的一部分, 每次編譯工程的時候, 數據庫部分也會自動地重新構造。這真的太棒了。你要做的事情基本上減到了0。如果數據庫結構定義發生了改變, 并在編譯時自動更新了數據訪問層的代碼,那么程序中任何使用過時的舊代碼的地方, 都會引發編譯錯誤。
數據訪問層是個很好的例子, 這樣的例子還有好多。從GUI樣板代碼, WEB代碼, COM和CORBA存根, 以及MFC和ATL等等。在這些地方, 都是有好多相似代碼多次重復。既然這些代碼有可能自動編寫, 而程序員時間又遠遠比CPU時間昂貴, 當然就產生了好多工具來自動生成樣板代碼。這些工具的本質是什么呢? 它們實際上就是制造程序的程序。它們有一個神秘的名字, 叫做元編程。所謂元編程的本義, 就是如此。
元編程本來可以用到無數多的地方, 但實際上使用的次數卻沒有那么多。歸根結底, 我們心里還是在盤算, 假設重復代碼用拷貝粘貼的話, 大概要重復6,7次, 對于這樣的工作量,值得專門建立一套生成工具嗎? 當然不值得。數據訪問層和COM存根往往需要重用數百次,甚至上千次, 所以用工具生成是最好的辦法。而那些僅僅是重復幾次十幾次的代碼, 是沒有必要專門做工具的。不必要的時候也去開發代碼生成工具, 那就顯然過度估計了代碼生成的好處。當然, 如果創建這類工具足夠簡單的話, 還是應當盡量多用, 因為這樣做必然會節省時間。現在來看一下有沒有合理的辦法來達到這個目的。
現在, C預處理器要派上用場了。我們都用過C/C++的預處理器, 我們用它執行簡單的編譯指令, 來產生簡單的代碼變換(比方說, 設置調試代碼開關), 看一個例子:
#define triple(X) X+X+X
這一行的作用是什么? 這是一個簡單的預編譯指令, 它把程序中的triple(X)替換稱為X+X+X。例如, 把所有的triple(5)都換成5+5+5, 然后再交給編譯器編譯。這就是一個簡單的代碼生成的例子。要是C的預處理器再強大一點, 要是能夠允許連接數據庫, 要是能多一些其他簡單的機制, 我們就可以在我們程序的內部開發自己的數據訪問層。下面這個例子, 是一個假想的對C宏的擴展:
#get-db-schema("127.0.0.1")
#iterate-through-tables
#for-each-table
class
#table-name
{
};
#end-for-each
我們連接數據庫結構定義, 遍歷數據表, 然后對每個表創建一個類, 只消幾行代碼就完成了這個工作。這樣每次編譯工程的時候, 這些類都會根據數據庫的定義同步更新。顯而易見, 我們不費吹灰之力就在程序內部建立了一個完整的數據訪問層, 根本用不著任何外部工具。當然這種作法有一個缺點, 那就是我們得學習一套新的”編譯時語言”, 另一個缺點就是根本不存在這么一個高級版的C預處理器。需要做復雜代碼生成的時候, 這個語言(譯者注: 這里指預處理指令, 即作者所說的”編譯時語言”)本身也一定會變得相當復雜。它必須支持足夠多的庫和語言結構。比如說我們想要生成的代碼要依賴某些ftp服務器上的文件, 預處理器就得支持ftp訪問, 僅僅因為這個任務而不得不創造和學習一門新的語言,真是有點讓人惡心(事實上已經存在著有此能力的語言, 這樣做就更顯荒謬)。我們不妨再靈活一點, 為什么不直接用 C/C++自己作為自己的預處理語言呢? 這樣子的話, 我們可以發揮語言的強大能力, 要學的新東西也只不過是幾個簡單的指示字 , 這些指示字用來區別編譯時代碼和運行時代碼。
<%
cout<<"Enter a number: ";
cin>>n;
%>
for(int i=0;i< <% n %>;i++)
{
cout<<"hello"<<endl;
}
你明白了嗎? 在<%和%>標記之間的代碼是在編譯時運行的, 標記之外的其他代碼都是普通代碼。編譯程序時, 系統會提示你輸入一個數, 這個數在后面的循環中會用到。而for循環的代碼會被編譯。假定你在編譯時輸入5, for循環的代碼將會是:
for
(
int
i=0;i<5; i++)
{
cout<<
"hello"
<<endl;
}
又簡單又有效率, 也不需要另外的預處理語言。我們可以在編譯時就充分發揮宿主語言(此處是C/C++)的強大能力, 我們可以很容易地在編譯時連接數據庫, 建立數據訪問層, 就像JSP或者ASP創建網頁那樣。我們也用不著專門的窗口工具來另外建立工程。我們可以在代碼中立即加入必要的工具。我們也用不著顧慮建立這種工具是不是值得, 因為這太容易了, 太簡單了。這樣子不知可以節省多少時間啊。
以上就是關于“Lisp的本質是什么”這篇文章的內容,相信大家都有了一定的了解,希望小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。