嵌套vs线程vs让

时间:2019-04-03 16:05:29

标签: clojure

这里是Clojure新手,您认为以下哪种形式最“ clojuresque”:

  1. 重嵌套

    (my-fn3 (my-fn2 (my-fn1 foo bar) baz) qux)
    
  2. 使用let

    (let [result foo
          result (my-fn1 result bar)
          result (my-fn2 result baz)
          result (my-fn3 result qux)])
    
  3. 使用线程优先

    (-> foo
        (my-fn1 bar)
        (my-fn2 baz)
        (my-fn3 qux))
    

2 个答案:

答案 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中发现代码的可读性反映了它的惯用法,如果外观正确,几乎总是有一种更好的编写方法。