如何阅读心理Lisp / Clojure代码

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

注意:已经是一个维基

我是function编程的新手,虽然我可以在函数式编程中读取简单的函数,例如计算一个数的阶乘,但是我发现很难读大函数。 部分原因是我认为是因为我无法弄清函数定义中的小块代码,还有一部分原因是我在代码中匹配( )变得困难。

如果有人能通过阅读一些代码,并给我一些关于如何快速解密一些代码的提示,那将是非常好的。

注意:如果我盯着它10分钟,我可以理解这个代码,但是我怀疑这个代码是否用Java编写,这需要我10分钟。 所以,我觉得在Lisp风格的代码中感觉很舒服,我必须更快地做到这一点

注:我知道这是一个主观的问题。 而我在这里并没有寻求任何可以证实的正确答案。 只是评论你如何去读这个代码,将是受欢迎的,非常有帮助

 (defn concat ([] (lazy-seq nil)) ([x] (lazy-seq x)) ([xy] (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)))) ([xy & 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 xy) zs)))) 

Lisp代码,特别是由于常规的语法,比其他function语言更难以阅读。 Wojciech提供了一个很好的答案来提高你的语义理解。 这里有一些关于语法的帮助。

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

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

其次,如果你不能把所有东西都放在一行上,那么把下一行缩进less量。 这几乎总是两个空间:

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

第三,如果一个函数的多个参数不能放在一行上,请将第一个括号下的第二个,第三个等参数排成一行。 许多macros有一个类似的规则,允许重要的部分首先出现。

 ; 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)) 

这些规则可以帮助您在代码中find块:如果您看到了

 (chunk-cons (chunk-first s) 

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

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

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

如果你看到上面的defn concat ,你知道你有三个块,因为同一层有三件事。 但是,第三行以下的所有内容都在其下面缩进,所以其余的属于第三块。

这是一个Scheme的风格指南 。 我不知道Clojure,但大多数规则应该是相同的,因为其他Lisp没有太多差别。

我认为concat是一个很好的例子。 这是一个核心function,它比你通常自己编写的代码更低级,因为它努力提高效率。

另外要记住的一点是,与Java代码相比,Clojure代码非常密集。 一点Clojure代码做了很多工作。 在Java中相同的代码不会是23行。 它可能是多个类和接口,很多方法,大量的本地临时丢弃variables和难以理解的循环结构以及各种各样的样板。

一些一般的技巧,但…

  1. 大部分时间尝试忽略这些parens。 使用缩进,而不是(如内森桑德斯build议)。 例如

     (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. 如果你把你的光标放在paren上,而你的文本编辑器没有语法高亮的匹配,我build议你找一个新的编辑器。

  3. 有时它有助于从内部读取代码。 Clojure代码往往是深深嵌套。

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

    不好: 所以我们从1到10的数字开始。然后我们倒过了等待补码过滤映射的顺序,我忘了我在说什么。

    好: “好的,所以我们需要一些xs (complement even?)意味着(complement even?)的相反,所以”奇怪“,所以我们过滤一些集合,所以只剩下奇数,然后我们把它们分开所有的都是17,然后我们把它们的顺序颠倒过来,而且这个xs是1到10,是gotcha。

    有时这有助于明确地做到这一点。 采取中间结果,把他们放在一个让给他们一个名字,让你明白。 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并且在你的类path上有你的.clj文件,你可以做(source some-function)并且看到它的所有源代码。 你可以做(show some-java-class)并看到它的所有方法的描述。 等等。

能够快速阅读的东西只有经验。 Lisp不比其他语言更难阅读。 恰巧大多数语言看起来像C,大多数程序员大部分的时间都是在阅读,所以看起来C语言更容易阅读。 练习练习练习。

首先记住function程序由expression式组成,而不是语句。 例如,form (if condition expr1 expr2)将其第一个arg作为条件来testing布尔值,评估它,如果评估为true,则评估并返回expr1,否则评估并返回expr2。 当每个表单都返回一个expression式时,像THEN或ELSE关键字这样的常见语法结构可能会消失。 请注意,在这里, if它本身也评估为一个expression式。

现在关于评估:在Clojure(和其他Lisp)中,你遇到的大多数表单都是函数调用的forms(f a1 a2 ...) ,其中f所有参数都在实际函数调用之前被求值; 但forms也可以是macros或特殊forms,而不是评估其一些(或全部)论点。 如果有疑问,请参阅文档(doc f)或只检查REPL:

user=> apply
#<core$apply__3243 clojure.core$apply__3243@19bb5c09>
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
user=> doseq
java.lang.Exception: Can't take value of a macro: #'clojure.core/doseq
一个macros。

这两条规则:

  • 我们有expression式,而不是语句
  • 可能会出现子表单的评估,取决于外部表单的行为

应该减轻你的Lisp程序的负担,尤其是。 如果他们像你给的例子一样有很好的缩进。

希望这可以帮助。