2016-03-18 118 views
1

考慮這個來自SICP的迭代因子過程。如何格式化lisp代碼?

(define (fact-iter product counter max-count) 
    (if (> counter max-count) 
     product 
     (fact-iter (* counter product) 
       (+ counter 1) 
       max-count))) 

在這裏,我們可以看到:

  • 階乘的聲明不具有前導空格。我認爲這很正常。
  • 該函數的主體每行需要兩個前導空格。
  • 我們在if語句的第一個子句和第二個子句的開始處添加了四個空格。總共6個空間。
  • 我們在最後兩行中添加了11個空格,if語句的第二個子句的其餘部分。共有17個空間。

這是爲什麼?間距讓我感到困惑。爲什麼不能像java(在代碼的每個內部部分添加四個空格)?我應該如何格式化lisp代碼?

+0

函數調用的參數與第一個參數對齊。這說明了它們的參數,所以你不需要開始計算parens來計算出'MAX-COUNT'是'FACT-ITER'的第三個參數(即使它是'IF'也是如此當然不是函數)。它也將參數列表與宏觀主體區分開來。 – jkiiski

+0

@jkiiski我們爲「產品」添加了4個空格來對齊它(如果?是這樣嗎?爲什麼只添加兩個空格到「(if」?爲什麼是2? – morbidCode

+0

@morbidCode:某些'特殊'形式,例如定義和綁定形式有其自己的規則,它們是(我認爲:我相信編輯知道)表單的「主體」被兩個字符縮進。這包括Scheme中的define,CL中的defun和let或lambda。這些通常是「身體」味覺的概念。 (注意我在這裏以非技術意義使用'特殊')。 – tfb

回答

8

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的語法並沒有真正地說明這一點。

一開始計劃縮進看起來有點時髦,但一旦您習慣了它,它使代碼更容易看到,而不必在頭腦中加上圓括號。語法的一致性既是祝福也是詛咒:它使得編寫宏變得微不足道,但它消除了一些使代碼更容易理解的可視化標記。擁有更多語義縮進系統有助於緩解這一缺陷。

+1

是否有自動執行此操作的工具? – morbidCode

+2

@morbidCode是的,但它取決於你的編輯器。 DrRacket具有自動縮進功能。 Emacs有各種編輯不同類型lisp代碼的主要模式。其他編輯有類似的功能。 –

6

Lisp有一些縮進代碼的規則。使用最緊湊的版本。列表中元素的對齊很重要。考慮水平空間與可讀性的最佳使用。

  • 宏和特殊形式可以定製縮進規則。見下文。

  • 功能有一些壓痕變體基於可用水平空間

實施例:

(append a b c) ; all arguments fit on a line 

(append a   ; arguments are aligned 
     b 
     c) 

(append   ; saving horizontal space, elements are aligned 
a 
b 
c) 

一個簡單的宏/操作者像IF THEN ELSE像函數通常對準。

稍微更復雜的情況是DEFINE:

(define (FUNCTION-NAME ARG0 ... ARGN) BODY-FORM-0 ... BODY-FORM-N) 

例子:

(define (foo a b) (print a) (append a b)) 

(define (foo a b) 
    (print a) 
    (append a b)) 

(define (foo a 
       b) 
    (print a) 
    (append a b)) 

(define (foo 
      a 
      b) 
    (print a) 
    (append a b)) 

一個典型的Lisp的漂亮打印機將選擇壓痕變種基於可用的水平空間。