雖然對絕大多數的語言來說,都會提出一定的亮點,強調其可用性以吸引開發人員投入,然而,這世界還存在著一些看到時會令人抓狂的語言,設計者看來根本就不在乎語言的可用性。
雖然可以單純地當這類語言只是在開玩笑,根本做不了什麼正經事,然而,也可以進一步認真地思考,這類語言究竟為什麼行得通?
只使用6個字元的JSFuck
如果你關注JavaScript,前些日子也許有注意到JSFuck這個東西,它號稱只要用JavaScript語法中的6個字元「[]()!+」,寫出來的程式就可以被JavaScript環境執行,想要親身體驗看看的話,可以在www.jsfuck.com中的方框,輸入alert(1),或者任何有效的JavaScript程式碼,按上Encode按鈕後,就會看到轉換出來的[][(![]+[])[+[]]+([![]]+[][[...確實是由六個字元組成,按下Run this,或者複製後放到網頁中由瀏覽器讀取,還真的可以執行,這是怎麼辦到的?
身為稱職的開發者,當然會想知道JSFuck的原理,而直接檢視網站上的原始碼是最快速的途徑。在原始碼中可以看到,當按下Run this時,會執行eval($("output").value),此時,可以料想到的部分是,Encode按鈕按下編出來的6個字元組合程式碼,也就是$("output").value的值,應該會是'eval(alert(1))'的字串,如此eval('eval(alert(1))')才會出現警示方塊並顯示1的結果。
只是為什麼能使用6個字元產生'eval(alert(1))'?進一步看看jsfuck.js,馬上就會看到有個MAPPING物件,來看看'eval(alert(1))'的第一個字元'e'吧!MAPPING物件鍵為'e'的部份,對應的值是'(true+"")[3]',你應該知道JavaScript的弱型別特性,令它會有許多奇異的行為,true+""會是什麼呢?答案是一個'true'字串,[3]表示取索引3處的字元,結果就是'e'。
不過,true並不在那六個字元之中啊?如果你知道!![]在JavaScript中結果會是true,那麼,'(true+"")[3]'又可以轉換為'(!![]+"")[3]'。依照這類的方式取代下去,就可以將'eval(alert(1))'完全轉換為六個字元組合出來的結果。
jsfuck.js原始碼並不長,網站上未經壓縮的排版版本只有300多行,如果懶得看原始碼,可以看看〈A Javascript journey with only six characters〉(https://goo.gl/LOsOvW)這篇文章,最後的結論中談到,雖然eBay曾經有個漏洞,可以利用JSFuck轉出來的字串進行XSS攻擊,不過這攻擊方式並不常見,至於說是可當成程式碼混淆的一種,其實也還有更好的方式,用六個字元寫程式基本上沒什麼用途,作者最後還很幽默地說了聲「Sorry」。
只使用8個字元的Brainfuck
其實第一次看到JSFuck之時,我想起了以前曾經看過的程式語言Brainfuck,這門語言只使用到8個字元「><+-.,[]」,你想用Brainfuck來寫個Hello World!/n嗎?相關的程式碼內容,會是「++++++++++[>++++ +++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++. ------.--------.>+.>.」
喔喔!我幾乎可以聽到你脫口而出的髒話了,這也就是為什麼JSFuck與Brainfuck都有個fuck字眼了!
就Brainfuck來說,可想像它是一臺機器,擁有一個讀寫頭及無限長的紙帶,機器會依><+-.,[]來進行操作;同時,>、<分別讓讀寫頭在紙帶上前進、後退一格;+、-則對目前讀寫頭下的值,進行遞增、遞減1的行為;.是將目前讀寫頭下的值,以對應的字母輸出;,是將目前讀寫頭下的字母讀入並轉為數字後寫回;[會比較目前的值,若不為0即前進,若為0就略過指令、直到遇上];]也是比較目前的值,在不為0時,指令要跳回第一個遇上[的位置。
想要個簡單又具體例子?+++++++因為有7個+,這表示在目前格子遞增7次的1,結果就是得到數字7;想要重複執行的指令,可以寫在[與]之間,例如,+++++++[>++++++++++<-]>.表示重複7次(紙帶第一格因+++++++而寫入7),每次移至第二格(因>移至下一個)加10(++++++++++),接著,返回前一格、遞減1(<-),直到第一格的值為0為止([與]的判斷),在]之後會移至第二格(因為>)並輸出字母F(因為.),也就是70的對應字母。
依照相同的道理,你可以試著看看Brainfuck的Hello World!/n程式,確認是否真的輸出了對應的字母,這時,你也應該能用Brainfuck寫些其他的程式了,只是多數人不會想這麼做,而且只要程式設計能力不要太差,寫個可以執行Brainfuck的直譯器,搞不好會比用Brainfuck寫出個Hello World!/n程式來得快,像是一個C語言的實現,就只要100行左右!
Esoteric programming language
難以想像,怎麼會有人發明JSFuck、Brainfuck這種無腦的語言?那麼來看看更無腦的Whitespace程式碼(https://goo.gl/danmjt),這鏈結按下後,只會看到「Ask the user for their name. Then say hello.」這些文字,它們是程式碼?不不不!這些文字只是註解,反白你的網頁吧!在反白下呈現的空白,才是程式碼,它們是由Space、Tab、換行等組合而成。
像Brainfuck、Whitespace這類語言其實不少,這類語言被稱為Esoteric programming language,或者簡稱為Esolang,它們看似無用,實際上能完成的計算,與今日可看到的「正常的」程式語言,都是同等價的。也就是說,雖然撰寫與閱讀起來極端的困難,然而,正常的程式語言能解決的問題,基本上也能使用Esolang來解決。
因為Esolang這類語言,都是具有圖靈完備性(Turing completeness)的語言。什麼是圖靈完備性?其實Brainfuck與其說是程式語言,不如說是一臺機器的描述,這臺機器具有無限的儲存(Storage)(無限長的紙帶)、運算(Arithmetic)(像是遞增、遞減)、條件判斷(目前值是否為0)以及重複(Repetition)([與]提供的功能),由於Brainfuck這臺機器,具有這四個能力,所以,它是圖靈完備。
Esoteric programming language在維基百科,被翻譯為〈深奧的程式語言〉條目,其中寫到:「它們的設計被用於測試電腦語言設計的極限,作為一個概念的證明,或僅僅是一個玩笑。」開發者看到Esolang會感覺新奇,多半也當是個玩笑,沒有人會這樣寫程式,不是嗎?不過,如果能進一步探索,像是研究一下JSFuck原始碼,就會覺得與其說它神奇,不如說它反映或檢視了JavaScript語言的本質。
進一步瞭解計算的本質
雖然撰寫或閱讀Brainfuck這種機器的描述,感覺很容易讓人腦殘,不過真要研究這樣為什麼行得通,其實一點都不腦殘!因為,Brainfuck這種機器其實是一種圖靈機(Turing machine),而圖靈機是遠在真正的電腦發明之前,由英國數學家Alan Mathison Turing於1936年提出的抽象計算模型,是一臺假想的計算機器。
正如維基百科上〈圖靈機〉條目中寫到的:「基本思想是用機器來模擬人們用紙筆進行數學運算的過程」、「在紙上寫上或擦除某個符號;把注意力從紙的一個位置,移動到另一個位置」,Alan Turing認為「這樣的一臺機器,就能模擬人類所能進行的任何計算過程」。
如果對圖靈機有興趣,可以看看《Good Math》中,從有限狀態機(Finite State Machines)介紹開始的後續章節,你會知道,操作上看似簡單的圖靈機,只要能將輸出傳給另一圖靈機作為輸入,通過若干組合,就能完成更大更複雜的計算,也能知道實際上無法計算的問題是存在的,像是經典的停機問題(Halting problem)。
瞭解這些後,下次遇到Esolang,應該就不會只當是個玩笑,甚至你也可以試著創造自己的Esolang,然後寫個直譯器,送給不明就理的人開開玩笑呢!