如何阅读Lisp/Clojure代码
非常感谢所有漂亮的答案!不能仅将一个标记为正确 注意:已经有一个wiki了 我是函数编程新手,虽然我可以阅读函数编程中的简单函数,例如计算数字的阶乘,但我发现很难阅读大函数。 部分原因是因为我无法计算出函数定义中较小的代码块,部分原因是因为我很难在代码中匹配如何阅读Lisp/Clojure代码,clojure,lisp,Clojure,Lisp,非常感谢所有漂亮的答案!不能仅将一个标记为正确 注意:已经有一个wiki了 我是函数编程新手,虽然我可以阅读函数编程中的简单函数,例如计算数字的阶乘,但我发现很难阅读大函数。 部分原因是因为我无法计算出函数定义中较小的代码块,部分原因是因为我很难在代码中匹配() 如果有人能带我看完一些代码,并给我一些如何快速破译一些代码的提示,那就太好了 注意:如果我盯着它看10分钟,我就可以理解这段代码,但我怀疑这段代码是否是用Java编写的,这需要10分钟。所以,我认为,要在Lisp风格的代码中感觉舒适,我
()
如果有人能带我看完一些代码,并给我一些如何快速破译一些代码的提示,那就太好了
注意:如果我盯着它看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代码比其他函数式语言更难阅读。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行。它可能是多个类和接口、大量方法、大量本地临时丢弃变量和笨拙的循环结构,以及通常所有类型的样板文件
不过有一些一般性的提示
if foo
then if bar
then baz
else quux
else blarf
(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)