如何阅读Lisp/Clojure代码

如何阅读Lisp/Clojure代码,clojure,lisp,Clojure,Lisp,非常感谢所有漂亮的答案!不能仅将一个标记为正确 注意:已经有一个wiki了 我是函数编程新手,虽然我可以阅读函数编程中的简单函数,例如计算数字的阶乘,但我发现很难阅读大函数。 部分原因是因为我无法计算出函数定义中较小的代码块,部分原因是因为我很难在代码中匹配() 如果有人能带我看完一些代码,并给我一些如何快速破译一些代码的提示,那就太好了 注意:如果我盯着它看10分钟,我就可以理解这段代码,但我怀疑这段代码是否是用Java编写的,这需要10分钟。所以,我认为,要在Lisp风格的代码中感觉舒适,我

非常感谢所有漂亮的答案!不能仅将一个标记为正确

注意:已经有一个wiki了

我是函数编程新手,虽然我可以阅读函数编程中的简单函数,例如计算数字的阶乘,但我发现很难阅读大函数。 部分原因是因为我无法计算出函数定义中较小的代码块,部分原因是因为我很难在代码中匹配
()

如果有人能带我看完一些代码,并给我一些如何快速破译一些代码的提示,那就太好了

注意:如果我盯着它看10分钟,我就可以理解这段代码,但我怀疑这段代码是否是用Java编写的,这需要10分钟。所以,我认为,要在Lisp风格的代码中感觉舒适,我必须更快地完成它

注:我知道这是一个主观问题。我并不是在寻求任何可以证明正确的答案。如果您对如何阅读此代码发表评论,将是非常受欢迎的,并且非常有帮助的


首先要记住,函数式程序由表达式组成,而不是语句。例如,表单
(如果条件expr1 expr2)
将其第一个参数作为测试布尔值的条件,对其求值,如果求值为true,则求值并返回expr1,否则求值并返回expr2。当每个表单返回一个表达式时,一些常见的语法结构(如THEN或ELSE)关键字可能会消失。注意这里的
if
本身也计算为表达式

关于计算:在Clojure(和其他Lisp)中,您遇到的大多数表单都是
(f a1 a2…
)形式的函数调用,其中
f
的所有参数都是在实际函数调用之前计算的;但表单也可以是宏或特殊表单,它们不计算其某些(或全部)参数。如果有疑问,请查阅文档
(doc f)
,或者只需签入REPL:


用户=>应用
#
a函数

用户=>doseq
java.lang.Exception:无法获取宏的值:#'clojure.core/doseq

这两条规则:

  • 我们有表达,而不是陈述
  • 子窗体的求值可能发生,也可能不发生,这取决于外部窗体的行为
应该可以简化你对Lisp程序的摸索,特别是如果它们像你给出的例子那样有很好的缩进


希望这能有所帮助。

由于语法规则,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)
            (concat (chunk-rest s) y))
您知道第一行不是完整的表达式,因为下一行缩进在它下面

如果您从上面看到
defn concat
,您就知道您有三个块,因为在同一级别上有三个东西。但是第三行下面的所有内容都缩进到它下面,所以其余部分属于第三个块


。我不知道Clojure,但大多数规则应该是相同的,因为其他Lisp没有太大的差异。

我认为
concat
是一个不好的例子。它是一个核心函数,比通常自己编写的代码更低级,因为它力求高效

要记住的另一件事是,与Java代码相比,Clojure代码非常密集。一个小Clojure代码可以做很多工作。Java中的相同代码不会有23行。它可能是多个类和接口、大量方法、大量本地临时丢弃变量和笨拙的循环结构,以及通常所有类型的样板文件

不过有一些一般性的提示

  • 大多数时候尽量忽略父母。使用缩进代替(正如Nathan Sanders所建议的)。e、 g

    当我看到它时,我的大脑会看到:

    if foo
      then if bar
        then baz
        else quux
      else blarf
    
  • 如果您将光标放在一个paren上,并且您的文本编辑器没有突出显示匹配的文本编辑器,我建议您找到一个新的编辑器

  • 有时,从内到外阅读代码会有所帮助。Clojure代码往往嵌套得很深

    (let [xs (range 10)]
      (reverse (map #(/ % 17) (filter (complement even?) xs))))
    
    坏:“所以我们从1到10的数字开始。然后我们颠倒了等待的补码过滤的顺序。我忘了我在说什么。”

    好:“好的,我们取一些
    xs
    (补码偶数?
    的意思是偶数的反义词,所以是“奇数”。所以我们过滤一些集合,只剩下奇数。然后我们将它们全部除以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)
    
    很快你就可以在精神上不费吹灰之力地做这类事情了

  • 自由使用
    (doc)
    。在REPL上提供文档的好处怎么强调都不为过。如果使用
    clojure.contrib.repl-utils
    并将.clj文件放在类路径上,则可以执行
    (源函数)
    并查看所有
    if foo
      then if bar
        then baz
        else quux
      else blarf
    
    (let [xs (range 10)]
      (reverse (map #(/ % 17) (filter (complement even?) xs))))
    
    (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)