这里是Clojure新手,您认为以下哪种形式最“ clojuresque”:
重嵌套
(my-fn3 (my-fn2 (my-fn1 foo bar) baz) qux)
使用let
(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))
答案 0 :(得分:3)
我会根据情况使用所有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中的任何地方都未使用)代替下划线_
作为表达式值的虚拟接收者,因为下划线有时很难看清。 >>
符号不仅在代码中脱颖而出,而且看起来有点像命令行提示符,这提醒了打印操作的命令性,副作用性质。
答案 1 :(得分:0)
我会说,大多数人在编写Clojure时通常会尝试将方法2和3结合起来使用,仅仅是因为它提供了最大的可读性。
通常,您将要使用let绑定的值将在多个地方使用的地方,例如您将需要一个特定的值,该值将用于创建另一个绑定,并且该值也将在let的主体内部使用。
现在,方法3并非总是可以实现的,在某些情况下,您会遇到以下情况:两个函数调用的线程值不共享相同的序数位置,这将需要重新认为/构造代码略有不同,或者可以找as->运算符,我个人觉得这很丑陋。
我经常在Clojure中发现代码的可读性反映了它的惯用法,如果外观正确,几乎总是有一种更好的编写方法。