為什麼很多編程語言採用花括弧區分 block 而非縮進?

個人剛開始接觸一直是反感括弧的,特意去翻過wiki裡面提到的各種語言,發現早期的語言大多是採用end結尾結束代碼塊的,然後LISP一類的到處都是括弧。往後相當多的語言多是花括弧控制的塊,C、D、Go、Perl、Javascript、Java,到處都是。這麼廣泛出現應該有難以撼動的理由吧?


縮進是一種不可見字元。你不知道縮進的是空格還是tab,而且不同編輯器對tab的處理不同。所以用縮進非常容易產生編輯錯誤。對上下文無關文法的parser來說,縮進也不如顯示的block標識符容易處理。相對於begin/end來說,花括弧輸入更快。


簡單說,之所以大部分語言採用顯式的起止符號(begin/end、{/})而不是縮進來界定block,我認為是受限於詞法分析器的實現複雜度。

P.S. 猜測從美學角度作評論的各位可能都沒有寫過編譯器前端 :-)

在最簡單的情況下,解析器面對的就是一個一維的字元流。字元流首先由詞法分析器拆成token流,再交給解析器做語法分析。大部分情況下,空白符都是token之間的分隔符,直接丟棄即可,少數情況下(如字元串內的空白符)需要保留。從一維的字元流/token流中解析出層次結構,最方便的顯然就是begin/end或者{/}這樣的顯式起止標記。

從編程的方便性上來講,我也喜歡Python和Haskell的縮進形式。但對於編譯器前端開發來說,用縮進來表示block範圍,概念上看解析器面對的就不是一個簡單的一維字元流/token流了,而是一個二維的平面結構。這時詞法分析器對空白符的處理會大大複雜化,像lex/flex這種傳統的詞法分析器生成器無法很好地處理這種問題。這也是為什麼Python的parser採用了自己獨創的一套工具。像Quex等比較現代的詞法分析器,內置了對縮進的處理,會以event形式拋給應用層,交由應用層處理。


因為 C 的普及,使得之後的語言或多或少的借鑒了 C 的語法。你舉的例子裡面,除了 Perl,其他幾款的語法基本都是參照 C 設計的。

採用縮進方式區分 statement block 其實是少數,現在流行的也就 Python 和 Haskell 利用了這點。很多人並不習慣這樣(雖然實際使用中花括弧總是配合縮進使用的)。


我倒是覺得縮進很符合人的自然思維啊,比如寫C++容易錯的一點:

if (...)
...
else
...
... // 這句不在if...else...裡面

忘了括弧,邏輯直接就錯了,這還算是常見bug,有時候腦子一熱太關注縮進了還真很難查出來。

還有更極品的:

if (...)
if (...)
...
else
....

經典的 垂懸else 案例,事實上人家else跟的是裡面那個if (距離最近的if)。

這個bug就更難發現了。

之所以有這些bug,完全就是縮進才是程序員理解代碼的正常思維嘛!

所以說,建議就是對於C++這類代碼,能用花括弧就盡量用吧,即使裡面只有一行代碼。

至於題主的問題,我認為就是歷史原因吧,花括弧對解析器的開發也帶來了方便。

--------

段子很多,來個時興的,由蘋果的低級Bug想到的:


縮進的處理難度可能是次要的。

我沒看過哪本書專門講了縮進怎麼處理,不過我個人認為,縮進不是語法分析問題,而是詞法分析問題。一個一層的縮進就是Indent(1), 兩層的則是Indent(2),也就是詞法分析的時候符號類型多加一個參數而已,之後語法分析該怎麼處理還是怎麼處理,無非是:

(1)詞法分析時插入軟括弧;

(2)詞法分析和語法分析之間加一道流程;

(3)語法分析時才處理縮進。

也許原因僅僅是,實現者和標準制定者認為「大家都用花括弧,所以我也用」,於是這個規則就被所有編程語言繼承下來了。


用縮進的文法是上下文相關的,yacc 處理不了,人肉寫也很麻煩。

真的很麻煩,你去試試就知道了。


lisp函數式語言不應考慮,基本哲學不同。

只討論命令式語言,命令式語言中縮進和括弧解決同一個問題,如何把多條語句合併為一條。

一是通過每行的縮進,標記每個語句,它與上一句是否同一條語句。事實上標記為空白字元,並不是最佳選擇,因為可讀性太差(特別是跨屏),也易錯(不信你用記事本寫寫看)。如郵件和bbs中,使用&>來表示一次引文,&>&>來表示二次引文可能更好一點。lex詞法分析器也可以處理,相對難一點。

二是通過開始和結束符來標記。語言上來說用xx+end和{}等價,因為編譯時實際都會處理成一個符號。簡潔的{}取得了勝利,更多是由於C語言的普及。不過{}形成一個狀態,有時你會記不得自己是否在一個{}中,這時就有配對問題如上面的else if。因此大部分類C的程序都同時使用了縮進來編排代碼,提示自己是否在一個{}中。這個縮進只是一個提示,錯了會影響可讀性,但不影響正確性。

{}成對可以提供一些冗餘信息共編譯器檢查。


你用縮進而不是花括弧標識代碼塊,若採用壓縮工具壓縮代碼,容易產生解析錯誤。而這是現實中常見的任務,例如壓縮 javascript.

同時考慮這個例子(javascript):

i = j = 1;

k = 2;

if (i == j)

if (j == k)

console.log("i equals k");

else

console.log("i doesn"t equal j"); // WRONG!!

JavaScript 會解釋為:

if (i == j) {

if (j == k)

console.log("i equals k");

else

console.log("i doesn"t equal j"); // OOPS!

}

於是你不希望的事情發生了。


卧槽!很多語言生出來的時候,顯示器一行只有80個字元好不好!你可真大方!


首先說明,縮進確實在詞法階段就很容易處理掉,就算是一般的flex這樣的詞法分析工具,也可以加狀態機變化,比如說一個棧,將之變化成 open_bracket, close_bracket, 然後在語法該怎麼處理就怎麼處理,

非縮進有點好處就是,比如說你製作一個REPL, 方便的就是在一行內處理一條長語句,if xxx then yyy else print err end,一個簡單的REPL為了支持縮進,去詞法, 語法實在不是必要呢,

反正語言層面強制的縮進排版,這點交由IDE做更合適一點。


強制縮進的好處是,不用刻意另行縮進。從而強制統一了格式。

括弧的好處恰恰是更自由,讓你有可以參加IOCCC的餘地。


用{}這樣顯式的區分相對來說比較容易處理,我感覺受C影響比較大

自從學了Haskell之後,發現用縮進真的不錯,特別增加可讀性。


為什麼沒人說python?

這是我用過的唯一一個使用縮進而不是括弧來標註代碼塊的語言。python很好用,很強大,我很喜歡,但我唯一不能接受的就是它的縮進……尤其是在不同的IDE下,tab和4個空格真的會要人命的。

括弧很好啊,合理的縮進配合括弧,這是很清晰的代碼結構。


推薦閱讀:

為什麼說 C 語言是系統級編程的首選?
Linux 下有沒有像 Visual Studio 一樣的,有自動填充、提示語法錯誤、斷點調試等功能的 C/C++ IDE?
用 int 類型表示多個字母,把多個字母轉化為整數的原理是什麼?
為什麼說C++不是C的超集?
函數傳遞引用 與 直接操縱全局變數 消耗資源的區別?

TAG:編程語言 | C編程語言 | 縮進 |