Clojure新人在这里,您认为以下哪种形式的“clojuresque”:
(my-fn3 (my-fn2 (my-fn1 foo bar) baz) qux)
(let [result foo
result (my-fn1 result bar)
result (my-fn2 result baz)
result (my-fn3 result qux)])
(-> foo
(my-fn1 bar)
(my-fn2 baz)
(my-fn3 qux))
我根据具体情况使用所有3种技术。目标是选择使代码最清晰的技术(而不是最少的字符!)。
技术#2在调试时特别方便,因为您可以轻松打印出中间值。但是,我通常会给每个阶段一个不同的名称来澄清情况:
(let [x-1 foo
x-2 (my-fn1 x-1 bar)
x-3 (my-fn2 x-2 baz)
x-4 (my-fn3 x-3 qux)]
(println :x-1 x-1)
(println :x-2 x-2)
(println :x-3 x-3)
(println :x-4 x-4)
x-4) ; don't forget to return the final result!
关于调试的问题,我就是这样做的。首先,3个版本原始:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defn fa [x y] (+ x y))
(defn fb [x y] (* x y))
(defn fc [x y] {:x x :y y})
(def tgt 2)
(defn stack-nest
[foo]
(fc
(fb
(fa foo 3)
3)
99))
(defn stack-thread
[foo]
(-> foo
(fa 3)
(fb 3)
(fc 99)))
(defn stack-let
[foo]
(let [a foo
b (fa a 3)
c (fb b 3)
d (fc c 99)]
d)) ; don't forget to return the final result!
你不需要发明自己的dbg
函数,因为在the Tupelo library中已经有一些不错的选择。在这里,我们使用spyx
(间谍显式)宏打印结果:
(dotest
(spyx (stack-nest tgt))
(spyx (stack-thread tgt))
(spyx (stack-let tgt)))
(stack-nest tgt) => {:x 15, :y 99}
(stack-thread tgt) => {:x 15, :y 99}
(stack-let tgt) => {:x 15, :y 99}
然后我们使用spy
和标签添加调试信息:
(defn stack-nest
[foo]
(spy :fc (fc
(spy :fb (fb
(spy :fa (fa foo 3))
3))
99)))
:fa => 5
:fb => 15
:fc => {:x 15, :y 99}
(stack-nest tgt) => {:x 15, :y 99}
它有效,但它非常难看。线程表怎么样?在这里我们可以插入间谍,它更好一点:
(defn stack-thread
[foo]
(-> foo
(spy :foo)
(fa 3)
(spy :fa)
(fb 3)
(spy :fb)
(fc 99)
(spy :fc)
))
:foo => 2
:fa => 5
:fb => 15
:fc => {:x 15, :y 99}
(stack-thread tgt) => {:x 15, :y 99}
我们得到了我们想要的东西,但它有一些重复。此外,我们需要将每个(spy ...)
表达式放在一个单独的行上,因此线程宏->
将值发送到像(fa 3)
这样的计算和像(spy :fa)
这样的打印步骤。
我们可以使用it->
宏来简化它,如下所示:
(defn stack-thread-it
[foo]
(it-> foo
(fa it 3)
(fb it 3)
(fc 99 it)))
我们使用符号it
作为占位符。请注意,我们可以将线程值放在任何参数位置,如fc
的反向args所示。对于我们的调试,使用spyx
,因此表达式是自我标记的,我们得到:
(defn stack-thread-it
[foo]
(it-> (spyx foo)
(spyx (fa it 3))
(spyx (fb it 3))
(spyx (fc 99 it))))
foo => 2
(fa it 3) => 5
(fb it 3) => 15
(fc 99 it) => {:x 99, :y 15}
(stack-thread-it tgt) => {:x 99, :y 15}
当中间变量在let
表达式中时,我调试如下:
(defn stack-let
[foo]
(let [a foo
>> (spyx a)
b (fa a 3)
>> (spyx b)
c (fb b 3)
>> (spyx c) ]
(spyx (fc c 99))))
a => 2
b => 5
c => 15
(fc c 99) => {:x 15, :y 99}
(stack-let tgt) => {:x 15, :y 99}
请注意,最后一个函数fc
直接作为返回值(在let
之外)调用,但我们仍然可以使用spyx
打印其值。
请注意,我喜欢使用符号>>
(在Clojure中的任何地方未使用)而不是下划线_
作为表达式值的虚拟接收者,因为下划线有时很难看到。 >>
符号不仅在代码中脱颖而出,而且看起来有点像命令行提示符,提醒打印操作的必要性,副作用性质之一。
我想说,通常情况下,方法2和方法3的组合是大多数人在编写Clojure时所努力的,只是因为它提供了最大的可读性。
通常,您会想要使用let,其中您绑定的值将在多个位置使用,例如你将需要一个特定的值,用于创建另一个绑定,该值也将在let的主体内使用。
现在,方法3并不总是可以实现,在某些情况下,你会遇到这样一种情况,即两个函数调用对于你正在线程化的值没有共享相同的序号位置,这将需要重新思考/结构化代码略有不同,或者您可以找到as->运算符,我个人觉得这很难看。
我经常在Clojure中发现代码的可读性反映了它的惯用性,如果看起来正确,那么几乎总是有更好的方法来编写代码。