请分享您的想法,参加2024 Clojure状态调查!

欢迎!请查看关于页面,以了解更多有关如何使用的详细信息。

+4
Clojure

我断断续续地使用Clojure达两年之久,读过几本书,但就在前几天,我又重温了threading宏的指南,并最终注意到了as->。也许我太笨了,但我敢发誓,我在读过的任何示例中都从未看到过它的使用,也没有在任何书中被突出显示。使用它与使用不那么灵活的threading宏相比,会有很大的性能损失吗?

我一直享受将数据处理管道建立起来的体验,通过将东西放入->或->>(能够通过注释一行来移除一个步骤以进行测试真是太好了!),但是我在使所有我想与->或->>一起工作的函数变得融洽上感到越陷越深。我承认我已经走得很远,以至于要包装一个函数来改变它的参数顺序。我在使用as->上感到如此兴奋,以至于感觉肯定有什么缺点是我错过的。为什么这不是链式调用函数的方式呢?

5 个答案

+13

令人惊讶的是,没有人提到as->是为了在->内部使用而设计的(所有threading宏都在->内部使用)。在查看其他人的回答后,这里是我的预期

(-> 2
    (+ 1)
    (as-> x (* 2 x)) ; or just (->> (* 2))
    (range))

(-> thing
    (->> (fn2 bleh)) ; or (as-> x (fn2 bleh x))
    (fn3 blah))

如您所见,as->的参数顺序——即表达式符号体——非常适合线程操作,但在这两种情况下,您可以从->直接进入->>进行线程化,使线程化的值位于表单末尾。

as->在您只有一个或两个形式的情况下表现得尤为出色,这些形式在直线性线程中不希望将线程化的值放在开头或末尾。

(-> thing
    (fn1 arg2 arg3)
    (+ 13)
    (as-> q (fn2 arg1 q arg3))
    (fn3 second-arg))

或者,或许,您需要一个let来进行解构或其他操作。

(-> foo
    (processs-it :expand)
    (as-> m (let [{:keys [start end]} m]
              (- end start)))
    (categorize-interval))
by
我认为指出Stuart Sierra关于不使用`as->`的帖子是有用的:https://stuartsierra.com/2018/07/15/clojure-donts-thread-as


我坦白地说,我已经多次阅读这篇博客文章,但从未真正理解为什么Stuart宁愿将其`(fn ... var ...)`形式包裹在自己的函数中,仅仅是为了修正参数的顺序。
by
哎呀,我应该知道Stuart对于这个话题会有深刻的见解,这同时也加强了我提出的“as->在->内部”的观点。

他对那个令人烦恼的函数的评论意味着这个

(defn wrapper [ctx other args ...]
  (irritating other ctx args ...))

然后您可以在直线->线程表达式中使用这个包装器,而不需要使用as->
by
哦,抱歉,我的评论不够清晰。我没有理解“为什么”。为什么不能接受你俩指出的“as->在->内部”呢?
by
因为只是一串简单的 -> 或只是一串简单的 ->> 更容易阅读。所以编写一个简单包装函数可能会是提高可读性的最佳途径,尤其是如果您需要在多个 -> 链中使用它。
+11
by

在 Clojure 1.5 中添加了 as->,因此一些较老的基础书籍可能没有介绍它,但它肯定适用于创建具有混合顺序的函数的管道。

这些宏中没有任何一个会影响到性能,因为它们是宏——它们都在编译时重写代码,所以执行时您基本上所做的与通过简单嵌套函数是一样的。

user=> (as-> 2 x (+ x 1) (* 2 x) (range x))
(0 1 2 3 4 5)
user=> (macroexpand-1 '(as-> 2 x (+ x 1) (* 2 x) (range x)))
(clojure.core/let [x 2 x (+ x 1) x (* 2 x)] (range x))

个人而言,我很少使用 as->。一般来说,-> 或 ->> 对我执行的调用集足够了。由于有关 参数顺序 的一般建议,我不是执行一系列集合运算,就是执行一系列序列运算,但通常不会是混合操作。

+3
by

为什么这不是将函数连接起来的方法?

我也有同样的疑问,但说实话,我使用的 ->->>as-> 更多。我想这归结为简洁性。关于正常的线程宏的一个漂亮之处是,如果一个函数只有一个参数,您完全可以省略括号。所以

(-> 0 (dec) (* 5) (Math/abs))

可以写成:

(-> 0 dec (* 5) Math/abs)

是的,您可以将它发展到极致。当我发现自己编写像这样的事情时:

(->> thing fn1 (fn2 bleh) (#(fn3 % blah)))

我会停止编码,出去散步...

+1
by

我一直在使用,没有发现任何问题。此外,通过查看宏的代码,它看起来很简单,没有任何缺点。

0
by

as->非常棒,但它的实现与->->>不同,可能会导致不同的行为。->->>递归地嵌套调用,而as->创建一系列let绑定。例如,(-> a long-running future)转换为(future (long-running a)),而(as-> a $ (long-running $) (future $))变为(let [_ a, _ (long-running _)] (future _))(它执行起来并不像看起来那么异步)。

在实际中,我没有发现这个问题,但这是关于在特定情况下使用as->的一个注意事项。

...