嵌套vs线程vs让

问题描述 投票:0回答:2

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))
clojure
2个回答
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中的任何地方未使用)而不是下划线_作为表达式值的虚拟接收者,因为下划线有时很难看到。 >>符号不仅在代码中脱颖而出,而且看起来有点像命令行提示符,提醒打印操作的必要性,副作用性质之一。


0
投票

我想说,通常情况下,方法2和方法3的组合是大多数人在编写Clojure时所努力的,只是因为它提供了最大的可读性。

通常,您会想要使用let,其中您绑定的值将在多个位置使用,例如你将需要一个特定的值,用于创建另一个绑定,该值也将在let的主体内使用。

现在,方法3并不总是可以实现,在某些情况下,你会遇到这样一种情况,即两个函数调用对于你正在线程化的值没有共享相同的序号位置,这将需要重新思考/结构化代码略有不同,或者您可以找到as->运算符,我个人觉得这很难看。

我经常在Clojure中发现代码的可读性反映了它的惯用性,如果看起来正确,那么几乎总是有更好的方法来编写代码。

© www.soinside.com 2019 - 2024. All rights reserved.