2009-12-12 45 views
70

非常感謝所有美麗的答案!不能標記只有一個是正確的如何閱讀心理Lisp/Clojure代碼

注:已經

我是新來的函數式編程和維基,而我可以讀函數式編程簡單功能,例如計算一個數的階乘,我發現很難讀大函數。 部分原因是我認爲是因爲我無法弄清函數定義中的小塊代碼,還有一部分原因是我在代碼中難以匹配()

如果有人能夠通過閱讀一些代碼,並給我一些關於如何快速解密某些代碼的提示,那將是非常棒的。

注意:如果我盯着它10分鐘,我可以理解這段代碼,但我懷疑這個代碼是否用Java編寫,需要10分鐘。所以,我覺得在Lisp風格的代碼中感覺很舒服,我必須更快地做到這一點

注:我知道這是一個主觀的問題。我在這裏並沒有尋求任何可以證實的正確答案。只是你如何去閱讀此代碼的註釋,將受到歡迎和高度有益

(defn concat 
    ([] (lazy-seq nil)) 
    ([x] (lazy-seq x)) 
    ([x y] 
    (lazy-seq 
     (let [s (seq x)] 
     (if s 
      (if (chunked-seq? s) 
      (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) 
      (cons (first s) (concat (rest s) y))) 
      y)))) 
    ([x y & zs] 
    (let [cat (fn cat [xys zs] 
       (lazy-seq 
        (let [xys (seq xys)] 
        (if xys 
         (if (chunked-seq? xys) 
         (chunk-cons (chunk-first xys) 
            (cat (chunk-rest xys) zs)) 
         (cons (first xys) (cat (rest xys) zs))) 
         (when zs 
         (cat (first zs) (next zs)))))))] 
     (cat (concat x y) zs)))) 
+3

體驗?你習慣於閱讀Lisp代碼會更快。然而,關於Lisp的主要抱怨之一是很難閱讀,所以不要期望它很快變得直觀。 – 2009-12-12 18:39:57

+10

這不是一個簡單的功能。如果10分鐘後你能完全理解它,那就很好。 – 2009-12-12 22:57:58

回答

47

由於常規語法,Lisp代碼尤其比其他函數式語言更難以閱讀。 Wojciech爲提高語義理解提供了一個很好的答案。這裏有一些關於語法的幫助。

首先,在閱讀代碼時,不要擔心括號。擔心縮進。一般規則是相同縮進級別的東西是相關的。所以:

 (if (chunked-seq? s) 
     (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) 
     (cons (first s) (concat (rest s) y))) 

其次,如果你不能在一行中放入所有東西,請將下一行縮進少量。這是幾乎總是兩個空間:

(defn concat 
    ([] (lazy-seq nil)) ; these two fit 
    ([x] (lazy-seq x)) ; so no wrapping 
    ([x y]    ; but here 
    (lazy-seq   ; (lazy-seq indents two spaces 
     (let [s (seq x)] ; as does (let [s (seq x)] 

第三,如果多個參數的函數無法容納在一行,排隊的第二,第三,等第一下參數的出發括號。許多宏都有一個類似的規則,允許重要部分首先出現。

; fits on one line 
(chunk-cons (chunk-first s) (concat (chunk-rest s) y)) 

; has to wrap: line up (cat ...) underneath first (of (chunk-first xys) 
        (chunk-cons (chunk-first xys) 
           (cat (chunk-rest xys) zs)) 

; if you write a C-for macro, put the first three arguments on one line 
; then the rest indented two spaces 
(c-for (i 0) (< i 100) (add1 i) 
    (side-effects!) 
    (side-effects!) 
    (get-your (side-effects!) here)) 

這些規則幫助你的代碼中找到塊:如果你看到

(chunk-cons (chunk-first s) 

不要指望括號!檢查下一行:

(chunk-cons (chunk-first s) 
      (concat (chunk-rest s) y)) 

你知道,因爲下一行縮進它下面第一行是不是一個完整的表達。

如果您從上面看到defn concat,則知道您有三個塊,因爲同一級別上有三件事。但是第三行下面的所有內容都在其下面縮進,所以其餘的都屬於第三塊。

Here is a style guide for Scheme。我不知道Clojure,但大多數規則應該是相同的,因爲其他Lisp沒有太多差別。

+0

在你的第二個代碼示例中,我如何控制第五行的縮進,即(Emacs中的lazy-seq?),默認情況下,它與前一行的'y'對齊 – 2009-12-31 20:52:22

+0

對不起,我不知道。我不使用clojure,這個例子不能精確地轉換成Scheme。你可能會檢查一個像clojure-indent-offset這樣的變量。例如,對於Haskell,我必須添加'(haskell-indent-offset 2)我的自定義設置變量 – 2010-01-04 01:29:37

+0

這裏是Clojure的風格指南https://github.com/bbatsov/clojure-style-guide – 2016-09-30 15:15:08

7

首先要記住,功能性程序包括表情,不發言。例如,表格(if condition expr1 expr2)將其第一個參數作爲條件來測試布爾值,評估它,如果它被評估爲真,則評估並返回expr1,否則評估並返回expr2。當每個表單返回一個表達式時,像THEN或ELSE關鍵字這樣的常見語法結構可能會消失。請注意,這裏if本身也評估爲表達式。

現在關於評估:在Clojure(和其他Lisp)中,您遇到的大多數表格都是函數調用形式(f a1 a2 ...),其中f的所有參數在實際函數調用之前被求值;但是表單也可以是宏或者不評估其一些(或全部)參數的特殊形式。如有疑問,請查閱文檔(doc f)或只是檢查REPL:

user=> apply
#<core$apply__3243 [email protected]>
功能
user=> doseq
java.lang.Exception: Can't take value of a macro: #'clojure.core/doseq
宏。

這兩個規則:

  • 我們有表達,而不是報表
  • 可能會出現子窗體的
  • 評估與否,取決於如何外在形式表現

應該減輕你的Lisp的groking程序,尤其是。如果他們像你給的例子一樣有很好的縮進。

希望這會有所幫助。

58

我認爲concat是一個不好的例子,試圖去理解。這是一個核心功能,它比您通常自己編寫的代碼更低級,因爲它努力提高效率。

要記住的另一件事是,與Java代碼相比,Clojure代碼非常密集。一點Clojure代碼做了很多工作。在Java中相同的代碼不會是23行。它可能是多個類和接口,很多方法,大量本地臨時丟棄變量和難以理解的循環結構以及各種樣板文件。

雖然一些提示...

  1. 試圖忽略大部分時間的括號。改用縮進(如Nathan Sanders所建議的)。例如

    (if s 
        (if (chunked-seq? s) 
        (chunk-cons (chunk-first s) (concat (chunk-rest s) y)) 
        (cons (first s) (concat (rest s) y))) 
        y)))) 
    

    當我看着我的大腦認爲:

    if foo 
        then if bar 
        then baz 
        else quux 
        else blarf 
    
  2. 如果你把你的光標在括號上,你的文本編輯器沒有語法加亮匹配的一個,我建議你找一位新的編輯。

  3. 有時它有助於從內部讀取代碼。 Clojure代碼往往是深深嵌套的。

    (let [xs (range 10)] 
        (reverse (map #(/ % 17) (filter (complement even?) xs)))) 
    

    壞:「所以我們開始與數字1到10。然後我們扭轉了等待我忘了我在談論的補體過濾的映射的順序。」

    好:。「。好了,我們正在採取一些xs(complement even?)意味着甚至相反,因此‘奇’因此,我們篩選了一些收集所以只有奇數留然後我們將它們全部除以17,然後我們改變它們的順序,並且所討論的xs是1到10,是。「

    有時它有助於明確地做到這一點。拿中間結果,把它們放在let中,並給它們一個名字,這樣你就能理解。 REPL就是這樣玩的。執行中間結果並查看每個步驟爲您提供的內容。

    (let [xs (range 10) 
         odd? (complement even?) 
         odd-xs (filter odd? xs) 
         odd-xs-over-17 (map #(/ % 17) odd-xs) 
         reversed-xs (reverse odd-xs-over-17)] 
        reversed-xs) 
    

    很快,你將能夠在精神上做這樣的事情毫不費力。

  4. 自由使用(doc)。在REPL提供正確的文檔是非常有用的,這一點也不爲過。如果您使用clojure.contrib.repl-utils並在類路徑中包含.clj文件,則可以執行(source some-function)並查看其所有源代碼。你可以做(show some-java-class)並查看其中所有方法的描述。等等。

能夠快速閱讀的東西只有經驗。 Lisp並不比任何其他語言更難閱讀。恰巧大多數語言看起來像C,大多數程序員花費大部分時間閱讀它,所以看起來C語言更容易閱讀。練習練習練習。

+3

+1提到'源'功能,這一點的信息很難來通過clojure.contrib.repl-utils的其餘部分來查看我錯過了什麼 – vedang 2010-11-22 14:50:00

+3

+1將中間結果放入let中,使得代碼更具可讀性(對於Clojure新手) – 2014-03-04 16:26:53