TL;博士:它使表達的明確
之一的Lisp的定義功能,包括計劃嵌套,是你寫的代碼基本上是AST(抽象語法樹)。像Java這樣的語言有很多語法,因此它們需要解析器來正確地消除可能含糊不清的語法。 Infix操作員是這方面的一個典型例子。請看下面的代碼在Java中:
int x = 1 + y * 2;
代碼的文本表示肯定不會暗示任何樹形結構,但實際上,這種說法有一個規範的解析其實樹。這將是這個樣子:
=
/\
int x +
/\
1 *
/\
y 2
等效方案的代碼,而另一方面,使所有嵌套難以置信明確:
(define x (+ 1 (* y 2)))
通知明確分組如何創建一個非常明確的樹的表達。運營商優先規則不需要像大多數其他語言那樣。這種簡單性是一種有意的設計選擇,因爲當源代碼表示非常簡單時,編寫轉換它的宏是非常容易的。 Lisp傾向於大量使用宏,因爲與其他編程語言相比,由於操作AST的語法非常簡單,所以操作AST相對無痛。
有了這一切記住,壓痕規則可能會變得更加明顯:Lisp代碼通常是縮進以這樣的方式,這樣的AST的結構是立即可見。考慮你的fact-iter
例如功能的替代版本,使用了「更簡單」的縮進風格:
(define (fact-iter product counter max-count)
(if (> counter max-count)
product
(fact-iter (* counter product)
(+ counter 1)
max-count)))
在這種特殊情況下,縮進不是災難性的,但fact-iter
遞歸調用現在更加難以直觀解析。由於第一個參數不再與最後兩個參數對齊,因此Lisp/Scheme語法的一致性使得很難立即採用fact-iter
被三個參數調用的事實。
這至少可以通過只把所有的參數在不同的行有點問題:
(fact-iter
(* counter product)
(+ counter 1)
max-count)
這工作,實際上是可以接受的Lisp的風格。儘管如此,垂直空間往往是一個很大的浪費,並且它仍然使得AST立即變得難以立刻起作用,因爲縮進在視覺上顯着減少。
對於一個例子,其中使用「簡單」的壓痕模型是災難性的方案,考慮以下兩個等價表達式:
(string->number (if (string? x) x
(format "~a" x)))
(string->number (if (string? x) x
(format "~a" x)))
第一個例子保持AST。很容易看到format
調用是if
表單的「其他」情況,因爲它嵌套在該表單下。第二個示例不保留AST,並且乍一看不清楚format
的調用嵌套在if
中,還是僅僅是傳遞給string->number
的第二個參數。你可以看到Lisp的語法並沒有真正地說明這一點。
一開始計劃縮進看起來有點時髦,但一旦您習慣了它,它使代碼更容易看到,而不必在頭腦中加上圓括號。語法的一致性既是祝福也是詛咒:它使得編寫宏變得微不足道,但它消除了一些使代碼更容易理解的可視化標記。擁有更多語義縮進系統有助於緩解這一缺陷。
函數調用的參數與第一個參數對齊。這說明了它們的參數,所以你不需要開始計算parens來計算出'MAX-COUNT'是'FACT-ITER'的第三個參數(即使它是'IF'也是如此當然不是函數)。它也將參數列表與宏觀主體區分開來。 – jkiiski
@jkiiski我們爲「產品」添加了4個空格來對齊它(如果?是這樣嗎?爲什麼只添加兩個空格到「(if」?爲什麼是2? – morbidCode
@morbidCode:某些'特殊'形式,例如定義和綁定形式有其自己的規則,它們是(我認爲:我相信編輯知道)表單的「主體」被兩個字符縮進。這包括Scheme中的define,CL中的defun和let或lambda。這些通常是「身體」味覺的概念。 (注意我在這裏以非技術意義使用'特殊')。 – tfb