前段時(shí)間在公司以《在Go語言中引入lambda》表達(dá)式為題目,進(jìn)行了一次技術(shù)分享动分,效果差強(qiáng)人意(無奈臉)毅糟。可能有人一聽lambda澜公,還要在Go里引入姆另,第一反應(yīng)就是厭惡,心想坟乾,這幫邪教又想搞什么事情禍害我簡(jiǎn)潔的Go語言迹辐,企圖通過新增各種雞肋特性把Go拉入C++的深淵。因此甚侣,這些人肯定是對(duì)我嗤之以鼻的明吩,更不用說花寶貴的時(shí)間來聽相關(guān)分享了。來聽的大部分同學(xué)都是吃瓜群眾殷费,好多甚至從來都不曾接觸過編譯原理印荔。實(shí)際上我在準(zhǔn)備PPT的時(shí)候也在想,在短短的一小時(shí)详羡,到底要分享哪些內(nèi)容仍律,才能讓大家都不虛此行呢,畢竟編譯原理是太復(fù)雜高深的領(lǐng)域实柠。事實(shí)上染苛,這確實(shí)很難。
很多編程開發(fā)者可能都疑惑過,自己平時(shí)使用的語言到底是怎么發(fā)明的茶行。Go一開始是用C寫的,C又是用啥寫的登钥?好像又沒聽說過用啥寫的畔师,隱約記得跟匯編有關(guān)系,難道是用匯編寫的牧牢?那匯編又是啥寫的看锉,寫匯編的又是啥寫的?感覺無限循環(huán)了塔鳍,就像先有雞還是先有蛋的問題…
很久之前伯铣,我也思考過這樣的問題,覺得好高深莫測(cè)轮纫,于是望而卻步腔寡。編譯原理入門的曲線比較陡,一開始各種各樣的概念會(huì)讓你頭暈眼花掌唾,于是很多人去網(wǎng)上求助如何學(xué)習(xí)編譯原理放前。不幸的是,網(wǎng)上很多人為了裝X不說實(shí)話糯彬,或者他們其實(shí)也不懂凭语,完全就是誤人子弟。隨便一搜你能發(fā)現(xiàn)撩扒,無數(shù)的答案給初學(xué)者推薦“龍書”(編譯原理領(lǐng)域的權(quán)威)似扔。不否認(rèn)它是一部巨著,但是給初學(xué)者看這個(gè)書完全就是抹殺他們的興趣搓谆。很多人忘了炒辉,他們學(xué)習(xí)數(shù)學(xué)也不是一開始就學(xué)微積分,也是從小學(xué)一年級(jí)課本從1+1開始學(xué)的挽拔。所以我建議辆脸,學(xué)習(xí)編譯原理千萬不要一開始就去讀這些大部頭,讀這些書唯一的作用就是讓你知難而退螃诅。
言歸正傳啡氢,本文是對(duì)之前分享的一個(gè)總結(jié)和補(bǔ)充,希望用最簡(jiǎn)潔的文字术裸,讓大家都認(rèn)識(shí)這個(gè)領(lǐng)域倘是,都有興趣去“把玩”相關(guān)技術(shù)。
學(xué)習(xí)編譯原理的第一件事情袭艺,是想清楚編譯技術(shù)到底是為了解決什么問題搀崭。只有想清楚了這個(gè)問題,你才能夠把握住它的主干脈絡(luò)。編譯原理的英文叫Programming Language Theory瘤睹,簡(jiǎn)寫為PLT升敲。很容易看出,其實(shí)它就是和編程語言強(qiáng)相關(guān)的一門技術(shù)轰传。它并不解決現(xiàn)實(shí)世界問題驴党,而是解決編程語言的問題。那么編程語言有什么問題呢获茬?
表達(dá)力
表達(dá)力是編程語言面臨的第一個(gè)問題港庄。那什么是表達(dá)力呢?大家都聽說過計(jì)算機(jī)剛被發(fā)明的時(shí)候恕曲,計(jì)算機(jī)讀取紙袋鹏氧,根據(jù)一行的某些位置有孔還是無孔,決定接下來執(zhí)行什么指令佩谣。假設(shè)一行有8個(gè)“槽”把还,那么計(jì)算機(jī)能識(shí)別的指令一共不超過2^8=256個(gè)。比如01010100代表執(zhí)行一個(gè)加法稿存,00011000代表執(zhí)行一個(gè)減法…笨篷。可以想象瓣履,最終你的代碼將會(huì)變成這樣:
01010101
11101000
10101010
10000111
# ...
如果代碼是這樣的率翅,我敢保證你很快就不知道自己在寫什么了,這里面不止有指令袖迎,還有參與計(jì)算的數(shù)冕臭。顯而易見,在紙帶上寫0101來編程沒有任何表達(dá)力燕锥,它沒有任何抽象能力辜贵,也幾乎沒有任何人能看懂它。正如我們看書做筆記一樣归形,一種顯而易見的幫助你理解二進(jìn)制代碼的方法也是做筆記托慨,比如:
01010101 #ADD
11101000 #232
10101010 #170
10000111 #...
這樣便容易理解多了,因此你可以現(xiàn)在紙上寫好代碼暇榴,確認(rèn)好邏輯再開始給紙袋打洞厚棵,剩下打洞的過程就是機(jī)械的重復(fù)了。
誒0簟婆硬!等等,既然我們的注釋和代碼實(shí)際是一一對(duì)應(yīng)的奸例,那么我們能不能編寫一個(gè)程序彬犯,讓它來完成這個(gè)簡(jiǎn)單的翻譯呢?之后我們輸入ADD這樣的符號(hào),用一個(gè)軟件把它翻譯成對(duì)應(yīng)的機(jī)器指令谐区。這便是最早的匯編器湖蜕。它其實(shí)一點(diǎn)也不復(fù)雜,因?yàn)镃PU廠商已經(jīng)規(guī)定好了卢佣,01010101代表做加法重荠,那么每次遇到ADD就把它替換成01010101。用現(xiàn)在的話來說虚茶,匯編器其實(shí)就是一個(gè)字符串到二進(jìn)制串的轉(zhuǎn)換函數(shù)。相信稍微有點(diǎn)編程基礎(chǔ)的同學(xué)都能用高級(jí)語言實(shí)現(xiàn)出來仇参。有了匯編器之后嘹叫,代碼煥然一新:
ADD 0 1
DEC 5 8
JMP 10
比起之前的01串,現(xiàn)在的代碼好懂多了诈乒。雖然用現(xiàn)在的高級(jí)語言實(shí)現(xiàn)匯編器非常簡(jiǎn)單罩扇,但如果限制只能使用機(jī)器碼來開發(fā)這個(gè)程序,那將變得很困難怕磨。但是喂饥,它并不是一個(gè)不可完成的任務(wù),只要多花些時(shí)間肠鲫,依然是能夠開發(fā)出來的(在開發(fā)第一個(gè)匯編器之前员帮,實(shí)際上可以先在紙上寫匯編代碼,在紙上完成編程导饲,然后通過人腦進(jìn)行匯編到機(jī)器碼的轉(zhuǎn)換捞高,最終輸入到計(jì)算機(jī)里)≡酰總之硝岗,通過一系列努力,我們終于不用再寫機(jī)器碼了袋毙。你可以看到型檀,這便是表達(dá)力的提升。
代碼復(fù)用
有了匯編之后大家的工作效率比以前提高了不少听盖,但是問題也隨之而來胀溺。比如在一個(gè)游戲程序中,我們經(jīng)常需要計(jì)算兩個(gè)點(diǎn)之間的距離媳溺,從而判斷兩個(gè)物體是否相撞月幌。回憶一下距離公式:
dist = 根號(hào)下((x1-x2)^2+(y1-y2)^2)
計(jì)算距離的匯編偽代碼如下:
DEC X1 X2
MUL X2 X2
DEC Y1 Y2
MUL Y2 Y2
ADD X2 Y2 # Y2保存了平方和
... # 剩下很多行代碼用于開根號(hào)
如果每次遇到要計(jì)算距離悬蔽,都把這段代碼寫一遍扯躺,顯然也是一件很痛苦的事情。而且你也很容易看到,即使是匯編代碼录语,即使是解決計(jì)算距離這么個(gè)簡(jiǎn)單的問題倍啥,也需要很多行代碼,而且一旦把代碼放在一起澎埠,你很難看明白它是在干什么虽缕。那么如之前一樣,一個(gè)簡(jiǎn)單的辦法是做筆記蒲稳,比如:
#CalculateDistance
DEC X1 X2
MUL X2 X2
DEC Y1 Y2
MUL Y2 Y2
ADD X2 Y2
...
---CalculateDistance
這就是“過程”的概念——用一個(gè)符號(hào)代表一段代碼氮趋。那么我們能不能再開發(fā)一個(gè)軟件,能夠支持通過用一個(gè)符號(hào)來標(biāo)注“過程”江耀,之后可以使用那個(gè)符號(hào)代表“過程”剩胁。這個(gè)軟件的實(shí)現(xiàn)其實(shí)也很簡(jiǎn)單,每次遇到一個(gè)符號(hào)祥国,就用對(duì)應(yīng)的代碼來替換這個(gè)符號(hào)昵观,這其實(shí)就是現(xiàn)在所謂的宏。有了代碼復(fù)用舌稀,代碼可以進(jìn)一步簡(jiǎn)化啊犬,表達(dá)力進(jìn)一步增強(qiáng)。當(dāng)然代碼復(fù)用不僅只此…
抽象能力
支持了最簡(jiǎn)單的代碼復(fù)用壁查,軟件開發(fā)更容易了觉至。但是容易也是相對(duì)的,匯編代碼依然晦澀難懂潮罪。因?yàn)榈侥壳盀橹箍底唬a依然還只是機(jī)器碼的映射,操作的對(duì)象是一個(gè)一個(gè)的寄存器嫉到。你需要站在機(jī)器的角度去思考沃暗,完成一個(gè)數(shù)值計(jì)算要移動(dòng)哪些寄存器,哪個(gè)寄存器里放哪個(gè)變量何恶。那么我們希望的代碼長什么樣呢孽锥?能不能不關(guān)心CPU那一級(jí)的東西,不用去關(guān)心寄存器细层,用數(shù)學(xué)語言來描述程序邏輯惜辑。
比如說我要計(jì)算 3 + 5,寫匯編代碼時(shí)我需要指定:
- 3放到哪個(gè)寄存器
- 5放到哪個(gè)寄存器
- 結(jié)果放到哪里(不同實(shí)現(xiàn)不一樣疫赎,大多放到目標(biāo)寄存器)
那我們能不能實(shí)現(xiàn)一個(gè)簡(jiǎn)單的翻譯軟件盛撑,自動(dòng)給我分配兩個(gè)寄存器和插入運(yùn)算指令呢,比如:
1 + 3
翻譯成:
MOV $1 RAX
MOV $3 RBX
ADD RBX RAX
這個(gè)怎么實(shí)現(xiàn)呢捧搞?你仔細(xì)一想發(fā)現(xiàn)稍微有點(diǎn)困難了抵卫,雖然能直接把1翻譯成 "MOV 1 寄存器" 狮荔,把3翻譯成"MOV 3 寄存器","+" 也能翻譯成 "ADD 寄存器A 寄存器B"介粘。但是問題也顯而易見——順序殖氏。按照我們想要的寫法,加號(hào)在1 和 3 中間姻采, 如果先翻譯1 然后翻譯加號(hào)雅采,這時(shí)候另一個(gè)寄存器還不知道分配的啥,而且還得把加法這條指令插到后面一條指令之后慨亲。好吧婚瓜,說實(shí)話這個(gè)問題也不是那么復(fù)雜,要交換順序就交換唄刑棵,后一個(gè)指令要用什么寄存器我可以提前決定唄闰渔。但是問題又來了,如果算式稍微復(fù)雜一點(diǎn)呢铐望,比如:
1 + 3 * 2
啊哈?有點(diǎn)困難了茂附,因?yàn)槌朔ê统▋?yōu)先級(jí)比加減法高正蛙,這時(shí)候你想一想怎么翻譯呢?不過我估計(jì)到這里也難不住你营曼,你可以維護(hù)兩個(gè)棧的數(shù)據(jù)結(jié)構(gòu)來解決這個(gè)問題(ACM入門題)乒验。那么再復(fù)雜一點(diǎn)的算式呢,比如:
1 + 3 * (2 - 8 / (1^100+ 5 * 5))
Oops 蒂阱,這就觸及到我知識(shí)的盲區(qū)了……怎么辦呢锻全?其實(shí)還是有辦法的陈肛! 1 + 3這種寫法我們看著是比較習(xí)慣欢唾,但不一定非要這么寫嘛,我如果寫成這樣呢:
1 3 +
先是兩個(gè)操作數(shù)拼缝,然后是操作符妈踊,這種書寫方式用習(xí)慣了也沒什么問題了嚎,比如 1 + 3 * 2 換一種寫法就是:
1 3 2 * +
翻譯起來就非常簡(jiǎn)便了,直接按順序翻譯就行:
MOV $1 RAX
MOV $3 RBX
MOV $2 RCX
MUL RCX RBX
ADD RBX RAX
是吧廊营,一氣呵成歪泳。雖然實(shí)現(xiàn)這個(gè)翻譯程序很簡(jiǎn)單,但是就需要程序員在寫代碼的時(shí)候自己做一次轉(zhuǎn)換露筒,把我們習(xí)慣的表達(dá)式轉(zhuǎn)成上面說的表達(dá)方式呐伞,其實(shí)也就是所謂的“中綴表達(dá)式”轉(zhuǎn)“后綴表達(dá)式”。這同樣還有另一個(gè)巨大的好處慎式,就是翻譯程序不用考慮優(yōu)先級(jí)問題伶氢,比如 2 * ( 4 - 1) + 3趟径,寫成后綴表達(dá)式就是:
2 4 1 - * 3 +
雖然還是不如中綴表達(dá)式看著直觀,但這種代碼比直接操作寄存器好懂多了鞍历。這也算一個(gè)進(jìn)步舵抹!但是,你如果多寫幾個(gè)后綴表達(dá)式你就會(huì)發(fā)現(xiàn)劣砍,其實(shí)它還是挺難懂的惧蛹,為了看懂它你得在大腦中做很多轉(zhuǎn)換。那么有沒有更好懂而且實(shí)現(xiàn)容易的方式呢刑枝?
其實(shí)很簡(jiǎn)單香嗓,把后綴表達(dá)式改成前綴表達(dá)式就行了,比如上面的:
- 中綴表達(dá)式 :2 * ( 4 - 1) + 3
- 后綴表達(dá)式: 2 4 1 - * 3 +
- 前綴表達(dá)式: + 3 * 2 - 4 1
也許你會(huì)說装畅,r u kidding me? 這前綴表達(dá)式哪里好懂了靠娱,不也差不多嗎?但是掠兄,但是啊像云,如果加個(gè)括號(hào)做助記符,你看看:
- 沒有括號(hào):+ 3 * 2 - 4 1
- 加了括號(hào):+ 3 ( * 2 (- 4 1) )
這個(gè)翻譯起來也很簡(jiǎn)單:整個(gè)式子是做一個(gè)加法蚂夕, 第一個(gè)加數(shù)是 3迅诬,第二個(gè)加數(shù)是一個(gè)算式的結(jié)果,這個(gè)算式是一個(gè)乘法婿牍,第一個(gè)因數(shù)是2 第二個(gè)因數(shù)是一個(gè)算式侈贷,這個(gè)算式是一個(gè)減法,減數(shù)是4被減數(shù)是1等脂。
對(duì)吧俏蛮,很容易的,一個(gè)遞歸就解決問題了上遥〔迹看到這里,你是不是想起了LISP露该?想起LISP就對(duì)了睬棚,LISP代碼其實(shí)就是這樣的。當(dāng)然解幼,LISP的發(fā)明絕不是這么發(fā)明的抑党,它是λ演算進(jìn)化而來,一開始是為了研究人工智能的撵摆,底靠,沒錯(cuò),就是人工智能特铝,1958年…
但是另一個(gè)問題你得知道暑中,不論LISP是如何而來壹瘟,它不能超越時(shí)代的發(fā)展,在那個(gè)年代要去實(shí)現(xiàn)LISP解釋器鳄逾,用前綴表達(dá)式或許就是工程上最佳的選擇稻轨。但是用了前綴表達(dá)式,括號(hào)寫多了雕凹,也有一種brainfuck的感覺殴俱,但有些人就是喜歡,不過這是后話了……
到這里你可以發(fā)現(xiàn)枚抵,隨著我們對(duì)編程語言的基本需求线欲,我們總是能想出辦法:
通過實(shí)現(xiàn)一個(gè)小的翻譯軟件,能讓我們代碼編寫稍微更容易汽摹,然后通過稍微容易一點(diǎn)的編程語言李丰,我們又能實(shí)現(xiàn)一個(gè)新的能夠翻譯更具表達(dá)力的編程語言
這個(gè)翻譯軟件其實(shí)就是我們常用的編譯器。20世紀(jì)50年代這方面的技術(shù)(PLT)就開始不斷發(fā)展逼泣,到現(xiàn)在PLT已經(jīng)非常非常復(fù)雜了趴泌。龍、虎拉庶、鯨書便是這幾十年幾代人智慧的結(jié)晶踱讨,其復(fù)雜度可想而知。那么PLT到底研究什么呢砍的?
如果只用我上面介紹的部分,那么很關(guān)鍵的一點(diǎn)就是莺治,寄存器如何分配廓鞠。因?yàn)镃PU中通用寄存器有限(假設(shè)有8個(gè)),那么在面對(duì)比如這樣的算式:1+2+3+4+5+6+7+8+9+10谣旁,那么要怎么分配寄存器呢床佳?實(shí)際上你發(fā)現(xiàn)其實(shí)兩個(gè)就夠了,比如:
MOV $1 EAX
MOV $2 EBX
ADD EBX EAX #結(jié)果存到EAX中
MOV $3 EBX
ADD EBX EAX
MOV $4 EBX
ADD EBX EAX
# ...
其實(shí)就是不斷地重復(fù)利用寄存器榄审。當(dāng)然PLT研究的領(lǐng)域還有很多很多砌们,最重要的領(lǐng)域之一就是如何識(shí)別程序代碼。編譯器怎么“看懂”程序員的代碼搁进,并進(jìn)行準(zhǔn)確的翻譯浪感。這也就是阻礙我們學(xué)習(xí)PLT的第一道門檻。大多數(shù)相關(guān)書籍在這部分會(huì)介紹非常非常多的名詞饼问,經(jīng)常會(huì)讓人摸不著頭腦影兽。那么為什么會(huì)有這么多名詞呢,為什么一開始學(xué)PLT都要按照這個(gè)路子學(xué)呢莱革?
從一道簡(jiǎn)單的題目看PLT
其實(shí)你可以發(fā)現(xiàn)峻堰,從前文中講的語言的演進(jìn)讹开,實(shí)際上都是小步發(fā)展的,每一步都是基于之前的工作捐名,通過一個(gè)簡(jiǎn)單的語言實(shí)現(xiàn)一個(gè)稍微復(fù)雜一點(diǎn)的編譯器旦万,能夠識(shí)別更復(fù)雜一點(diǎn)但表達(dá)力更強(qiáng)的語言,再通過該語言實(shí)現(xiàn)更復(fù)雜一點(diǎn)的編譯器镶蹋,如此循環(huán)…但是隨著編程語言不斷地發(fā)展成艘,最終還是要落實(shí)到人來寫代碼,因此只有中綴表達(dá)式才是符合人類思維的舒適的語法梅忌。那么我們就要想辦法狰腌,實(shí)現(xiàn)一個(gè)能夠編譯中綴表達(dá)式的編譯器。不管你如何厲害牧氮,這其實(shí)都不是一個(gè)容易的工作琼腔。另一方面,編程語言和英語踱葛、法語也是語言丹莲,各種語言研究方法也加入其中。當(dāng)然尸诽,如果在這里講得太多甥材,顯然也沒有人愿意看。我們還是說點(diǎn)簡(jiǎn)單的內(nèi)容性含。
經(jīng)過多年的發(fā)展洲赵,我們可以把如何識(shí)別編程語言這件任務(wù)簡(jiǎn)化成以下一個(gè)非常容易理解的題目:
有一個(gè)字符串S,長度任意商蕴。有一個(gè)集合M叠萍,M中每個(gè)元素也是一個(gè)字符串mi。如果S可以由M中的字符串組合而成(M中的字符串可以重復(fù)使用多次)绪商,那么就是S是合法的串】凉龋現(xiàn)給定S和M,問S是否合法
比如S:"abcaacad"格郁,M:{"a", "abc", "cad"}腹殿,那么S可以被這樣劃分:(abc)(a)(a)(cad),所以對(duì)于M來說例书,S是一個(gè)合法的串锣尉。
其實(shí)編譯器做的也是這么一件事情,輸入一個(gè)xxx.java决采,實(shí)際上對(duì)編譯器來說就是個(gè)長度任意的字符串S悟耘。JAVA語法規(guī)范規(guī)定了JAVA的若干種語法,這些語法組成一個(gè)集合M织狐,編譯器最基本的任務(wù)就是判斷S是否是合法的串暂幼,這其實(shí)就是語法檢查筏勒。在進(jìn)行語法檢查的時(shí),實(shí)際上編譯器也把源文件按照語法做了一個(gè)劃分旺嬉,用樹型結(jié)構(gòu)表示管行。當(dāng)然和上述的簡(jiǎn)化模型不同,每一種語法規(guī)則描述的是一類字符串邪媳,而不是某一個(gè)捐顷。比如變量名,只要是字母打頭并且由字母雨效、數(shù)字或下劃線組成的迅涮,都符合”變量名“的規(guī)則。又比如函數(shù)簽名徽龟,只要符合某些規(guī)則的叮姑,都屬于合法的函數(shù)簽名。編譯器通過一次遍歷据悔,把源程序成功進(jìn)行了一個(gè)劃分传透,最后用樹的結(jié)構(gòu)表示,我們稱這個(gè)樹就叫抽象語法樹极颓。
從前面講的內(nèi)容你應(yīng)該知道朱盐,前綴表達(dá)式和后綴表達(dá)式都很容易翻譯成低級(jí)的代碼。那如果編譯器把中綴表達(dá)式也轉(zhuǎn)成了一棵樹菠隆,那么運(yùn)用大一數(shù)據(jù)結(jié)構(gòu)課第一次課后作業(yè)的知識(shí)兵琳,是不是只要進(jìn)行先序或者后序遍歷就能完成中綴到前綴、后綴的轉(zhuǎn)換骇径?轉(zhuǎn)換完也就可以開始翻譯了闰围,對(duì)吧。
當(dāng)然既峡,說得容易做著難。怎么構(gòu)建這顆中綴樹碧查?前面那道字符串劃分的題目怎么做运敢?如何解決這個(gè)問題,這么些年研究了很多方法忠售,都收錄于各大”編譯原理“書籍中传惠。歸根到底,就是引入一個(gè)自動(dòng)機(jī)的概念稻扬。說到自動(dòng)機(jī)我想到了一件趣事卦方,我高中的時(shí)候參加信息學(xué)競(jìng)賽,每次在判題系統(tǒng)提交題目泰佳,如果正確的話會(huì)顯示”AC“(Accepted)盼砍。后來有一天聽大佬們討論一個(gè)叫”AC自動(dòng)機(jī)“的算法尘吗,心中驚愕,還有這么牛逼的算法浇坐?是不是偷偷把標(biāo)準(zhǔn)答案從判題系統(tǒng)里讀出來睬捶,然后直接根據(jù)輸入直接輸出結(jié)果啊。弱弱地去問大佬近刘,招來了大佬一頓嘲諷:”AC自動(dòng)機(jī)不是自動(dòng)AC機(jī)“…
當(dāng)然自動(dòng)機(jī)是個(gè)很大的話題擒贸,里面涵蓋了非常多完備的數(shù)學(xué)理論,我這里也不多說了觉渴,感興趣的可以去看看相關(guān)資料介劫。總之案淋,通過各種算法座韵,我們始終是可以把一段代碼”無歧義“地劃分成一個(gè)一個(gè)語法的組合,最后在內(nèi)存中以樹的結(jié)構(gòu)保存哎迄,這顆樹叫抽象語法樹AST(Abstract Syntax Tree)回右。這其實(shí)也就是編譯器的”前端“。編譯器也分前后端漱挚,前端就是前述內(nèi)容翔烁,把源文件轉(zhuǎn)成AST。后端就是對(duì)這顆AST進(jìn)行某種方式地遍歷旨涝,然后把每個(gè)語法規(guī)則識(shí)別到的內(nèi)容轉(zhuǎn)成另一種語言蹬屹,一般就是匯編,但也有例外白华,比如JAVA就轉(zhuǎn)成了能在JVM上執(zhí)行的字節(jié)碼慨默。
別看著上面就是幾句話的事兒,但是怎么轉(zhuǎn)換弧腥,怎么對(duì)轉(zhuǎn)換內(nèi)容進(jìn)行優(yōu)化厦取,這是非常難的。比如:
-
a + 1 + 2
管搪,是不是可以直接優(yōu)化成a + 3
虾攻? - 函數(shù)中有
int a; a+=10;
但是后面又沒用到a,是不是可以把這段冗余代碼干掉更鲁? - 如果函數(shù)最后一個(gè)return語句是遞歸調(diào)用自己霎箍,是否可以給它改成非遞歸?
龍書有一大半都是在講這些內(nèi)容澡为,編譯器的開發(fā)者也在不斷研究這些內(nèi)容漂坏。想象一下,如果自己實(shí)現(xiàn)了一個(gè)C語言的編譯器,別人的代碼只需要換成我的編譯器進(jìn)行編譯顶别,最后程序運(yùn)行速度就能有數(shù)倍的提升谷徙,這是多么自豪的事情啊。
在Go中引入λ表達(dá)式
這是我分享中的另一個(gè)主題筋夏,當(dāng)然蒂胞,這只是一個(gè)玩票性質(zhì)的項(xiàng)目。你可能會(huì)問為什么要搞這個(gè)条篷,我只能說:”自己如果從頭造一個(gè)語言骗随,又沒有人用。寫scala是不可能寫scala的赴叹,改go的編譯器又不會(huì)鸿染,只有寫個(gè)內(nèi)置解釋器支持些語法糖,才能維持得了生活介樣幾乞巧。搞JVM的里面?zhèn)€個(gè)都是人才涨椒,說(技)話(術(shù))又好(先)聽(進(jìn)),比Go那幫stuck in 70's好多了“绽媒。所以加完lambda表達(dá)式之后效果如下:
import "github.com/caibirdme/yql"
dst := []int{1, 2, 3, 4, 5, 6, 7}
r := yql.Filter(`(v) => v > 3 && v <= 7`).Map(`(v) => v << 2`).Filter(`(v) => v % 8 == 0`).Call(dst)
s, err := r.Interface()
// s == []int{16, 24}
支持了Filter和Map蚕冬,每個(gè)Filter和Map里接收一個(gè)lambda表達(dá)式,這用起來就很函數(shù)式了是辕。當(dāng)然你會(huì)覺得沒有什么egg用囤热,但畢竟玩技術(shù)嘛,聊勝于無获三。性能嘛旁蔼,其實(shí)也還好,只有第一次會(huì)做語法解析疙教,之后轉(zhuǎn)成bytecode棺聊,后續(xù)的調(diào)用都是在內(nèi)置VM中實(shí)現(xiàn)。也就是說贞谓,和JAVA一樣限佩,只是代碼是在第一次運(yùn)行到這里再實(shí)時(shí)編譯的,之后再執(zhí)行到這里裸弦,就直接跑字節(jié)碼了祟同。當(dāng)然作為一個(gè)玩具項(xiàng)目,JAVA的Hotspot VM JIT這些高大上的技術(shù)當(dāng)然是沒有用了烁兰。但是作為一個(gè)非JAVA選手,倒是深刻地理解了JAVA為什么要做這些以及可能會(huì)怎么做徊都。
所以你也可以看到沪斟,PLT其實(shí)也能玩得很花,在玩的同時(shí)它能幫你更深刻地認(rèn)識(shí)編程語言的本質(zhì),學(xué)習(xí)的過程中經(jīng)常會(huì)有恍然大悟的感覺主之。PLT不僅僅是編譯器開發(fā)者的領(lǐng)域择吊,其實(shí)我覺得所有程序員都有必要了解一下,增加你的見識(shí)開闊你的眼界槽奕。同時(shí)几睛,對(duì)于JAVA程序員,也不用再懼怕面試時(shí)別人問你JVM了…
寫在最后
PLT其實(shí)是一門很”美“的學(xué)科粤攒,不要先去看龍書所森,找本簡(jiǎn)單有趣的書開始學(xué)習(xí)吧。強(qiáng)烈推薦《the little schemer》夯接,據(jù)說作者是王垠大佬的導(dǎo)師焕济。看了之后再買兩本在逼乎上被強(qiáng)烈吐槽的書盔几,比如《自制編程語言》晴弃、《自制編譯器》這類書,能夠快速讓你熟悉工作流逊拍,當(dāng)然知識(shí)體系是不可能有知識(shí)體系的(一般逼乎上說太水不推薦的書上鞠,我覺得都挺不錯(cuò)的。逼乎黨喜歡推薦大部頭讓你知難而退芯丧,好讓他們繼續(xù)裝逼)…稍微有點(diǎn)感覺之后芍阎,然后你自己就可以到處找資料,用你熟悉的語言實(shí)現(xiàn)一個(gè)蹩腳LISP方言注整,基本上就算是入門能曾。最后,你再去看看龍書肿轨,很多問題就逐漸明了了寿冕。