如何阅读精神上的Lisp/Clojure代码

use*_*855 73 lisp clojure

非常感谢所有美丽的答案!不能只标记一个是正确的

注意:已经是wiki

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

如果有人能够引导我阅读一些代码并给我一些关于如何快速破译某些代码的提示,那将是很棒的.

注意:如果我盯着它看了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))))
Run Code Online (Sandbox Code Playgroud)

Bri*_*per 57

我认为这concat是一个不好的例子,试图理解.它是一个核心功能,它比你通常自己编写的代码更低级,因为它努力提高效率.

另外要记住的是,与Java代码相比,Clojure代码非常密集.一个小的Clojure代码做了很多工作.Java中的相同代码不会是23行.它可能是多个类和接口,很多方法,许多本地临时抛弃变量和笨拙的循环结构以及通常各种样板.

一些一般提示虽然......

  1. 试着在大多数时候忽略parens.改为使用缩进(正如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))))
    
    Run Code Online (Sandbox Code Playgroud)

    当我看到我的大脑看到:

    if foo
      then if bar
        then baz
        else quux
      else blarf
    
    Run Code Online (Sandbox Code Playgroud)
  2. 如果你把光标放在paren上,你的文本编辑器没有语法高亮显示匹配的那个,我建议你找一个新的编辑器.

  3. 有时从内到外阅读代码会有所帮助.Clojure代码往往是深层嵌套的.

    (let [xs (range 10)]
      (reverse (map #(/ % 17) (filter (complement even?) xs))))
    
    Run Code Online (Sandbox Code Playgroud)

    糟糕:"所以我们从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)
    
    Run Code Online (Sandbox Code Playgroud)

    很快你就可以毫不费力地在精神上做这件事了.

  4. 自由使用(doc).在REPL上提供文档的有用性不容小觑.如果您clojure.contrib.repl-utils在类路径中使用并拥有.clj文件,则可以(source some-function)查看它的所有源代码.您可以(show some-java-class)查看其中所有方法的说明.等等.

能够快速阅读的东西只有经验.Lisp并不比任何其他语言更难阅读.事实上,大多数语言看起来都像C,大多数程序员大部分时间都在阅读,所以看起来C语法更容易阅读.实践练习.

  • 提及"源"功能的+1,这一点信息比它应该更难获得.现在浏览clojure.contrib.repl-utils的其余部分,看看我错过了什么. (3认同)
  • +1用于在let中抛出中间结果,使代码更具可读性(对于Clojure新手) (3认同)
  • 有人真的应该写一些文章,甚至写一本关于如何作为非 LISP 程序员(例如来自 Python)学习 LISP 的书。看起来它“如此”不同,以至于我们非 LISP 人员需要学习如何学习 LISP。经过这样的解释,似乎也没有那么深不可测了。之前,我看一眼 LISP 代码就想:“尝试有什么意义?”。 (2认同)

Nat*_*ers 48

特别是,由于常规语法,Lisp代码比其他函数语言更难阅读.Wojciech为改善您的语义理解提供了一个很好的答案.这里有一些关于语法的帮助.

首先,在阅读代码时,不要担心括号.担心缩进.一般规则是在相同缩进级别的事物是相关的.所以:

      (if (chunked-seq? s)
        (chunk-cons (chunk-first s) (concat (chunk-rest s) y))
        (cons (first s) (concat (rest s) y)))
Run Code Online (Sandbox Code Playgroud)

其次,如果你不能将所有东西都放在一行上,那么将下一行缩小一点.这几乎总是两个空格:

(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)]
Run Code Online (Sandbox Code Playgroud)

第三,如果函数的多个参数不能放在一行上,则排在第一个起始括号下面的第二个,第三个等参数.许多宏都有类似的规则和变体,以允许重要部分首先出现.

; 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))
Run Code Online (Sandbox Code Playgroud)

这些规则可以帮助您在代码中找到块:如果您看到的话

(chunk-cons (chunk-first s)
Run Code Online (Sandbox Code Playgroud)

不要算括号!检查下一行:

(chunk-cons (chunk-first s)
            (concat (chunk-rest s) y))
Run Code Online (Sandbox Code Playgroud)

你知道第一行不是一个完整的表达式,因为下一行是在它下面缩进的.

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

这是Scheme的样式指南.我不知道Clojure,但大多数规则应该是相同的,因为其他Lisps都没有太大变化.


Woj*_*rek 7

首先要记住,功能程序包含表达式,而不是语句.例如,form (if condition expr1 expr2) 将其第一个arg作为测试布尔值的条件,对其进行求值,如果它被eval'为true,则计算并返回expr1,否则求值并返回expr2.当每个表单返回一个表达式时,一些常见的语法结构(如THEN或ELSE关键字)可能会消失.请注意,此处if本身也会计算表达式.

现在关于评估:在Clojure(和其他Lisps)中,您遇到的大多数表单都是表单的函数调用(f a1 a2 ...),其中所有参数f都在实际函数调用之前进行求值; 但是表格也可以是宏或特殊形式,不会评估其一些(或全部)参数.如有疑问,请查阅文档(doc f)或只是检查REPL:

user=> apply
#<core$apply__3243 clojure.core$apply__3243@19bb5c09>
一个宏的功能.
user=> doseq
java.lang.Exception: Can't take value of a macro: #'clojure.core/doseq

这两条规则:

  • 我们有表达,而不是陈述
  • 根据外形如何表现,可以发生或不发生子形状的评估

应该简化你对Lisp程序的研究,尤其是 如果他们有一个很好的缩进,就像你给出的例子.

希望这可以帮助.