如果你有一个很长的表达式链,使用
let
。在任何语言中,长而复杂的表达式或者嵌套层数过深的表达式都难以阅读。下面这种写法就不太好:
(do-something (map :id (filter #(> (:age %) 19) (fetch-data :people))))
这稍微好一些:
(do-something (map :id
(filter #(> (:age %) 19)
(fetch-data :people))))
但这也是不好的:
fetch_data(:people).select{|x| x.age > 19}.map{|x| x.id}.do_something
如果我们正在阅读这个,我们需要知道什么?我们正在对“people”的一些属性的某个子集调用
do_something
。由于第一个和最后一个之间有太多的距离,所以这段代码很难阅读,到达最后时我们会忘记我们在看什么。
在Ruby中,
do_something
(或者产生我们最终结果的任何内容)在行末丢失了,因此很难确定我们对
people
做了什么。在Clojure的情况下,很明显我们正在执行
do-something
,但是很难确定我们正在执行什么操作,除非我们读完整个代码块。
任何比这个简单示例更复杂的代码都将变得相当痛苦。如果您所有的代码都像这样,那么扫描所有这些代码行会让您的脖子累得发疼。
我更喜欢这样的东西:
(let [people (fetch-data :people)
adults (filter #(> (:age %) 19) people)
ids (map :id adults)]
(do-something ids))
现在很明显:我先从“人”开始,四处闲逛,然后对他们进行一些“做某事”的操作。
你也许可以逃脱这个问题:
fetch_data(:people).select{|x|
x.age > 19
}.map{|x|
x.id
}.do_something
但至少我更愿意这样做:
adults = fetch_data(:people).select{|x| x.age > 19}
do_something( adults.map{|x| x.id} )
即使您的中间表达式没有良好的名称,使用let
也不是什么新鲜事。 (这种风格有时在Clojure自己的源代码中使用,例如defmacro
的源代码)
(let [x (complex-expr-1 x)
x (complex-expr-2 x)
x (complex-expr-3 x)
...
x (complex-expr-n x)]
(do-something x))
这可以在调试时提供很大的帮助,因为您可以通过执行以下操作在任何时候检查事物:
(let [x (complex-expr-1 x)
x (complex-expr-2 x)
_ (prn x)
x (complex-expr-3 x)
...
x (complex-expr-n x)]
(do-something x))
(filter #(expr1) (map #(expr2) expr3))
。你会从内部的expr3开始输入还是从filter开始? - Alexeyexpr1
和expr2
是命名函数,那么(filter #(expr1) (map #(expr2) expr3))
将被转换为(filter expr1 (map expr2 expr3))
。Clojure旨在消除一些老式Lisp中典型的多余括号。 - Brian Carper