2024年Clojure状态调查!分享您的想法。

欢迎!请参见关于页面,了解有关这是如何工作的更多信息。

+4
Clojure

我断断续续地使用Clojure已经两年多了,读过几本书,但前几天我又看了一遍threading macros指南,终于注意到了as->。也许我只是 blind,但我肯定我在读过的任何例子或书中都没见过它的使用,也没在书中被强调。与使用不那么灵活的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->的参数顺序expr symbol body非常适合threading,但在这两种情况下,你只需从->thread到->>,就可以在形式末尾获得threaded值。

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))
我认为指出Stuart Sierra关于不要使用 `as->` 的文章可能会有帮助:https://stuartsierra.com/2018/07/15/clojure-donts-thread-as


我坦白说,我读过这篇文章好几次,但始终不明白为什么Stuart会宁愿将`(fn ... var ...)`形式封装到自己的函数中,仅仅为了调整参数的顺序。
啊,我应该知道Stuart会有关于这个问题的睿智之言——这也加强了我提出的“在 -> 内使用 as->”的观点。

他关于封装那个令人烦恼的函数的评论意味着这样

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

然后你可以在一个直接的 -> 线程表达式中使用这个包装器,而无需使用 as->
哦,对不起,我的评论不是很清楚。我不明白“为什么”。为什么不能像你们俩指出的那样接受“在 -> 内使用 as->”呢?
因为只有 -> 的简单链或只有 ->> 的简单链更容易阅读。因此,为函数编写一个简单的包装器可能是提高可读性的最佳途径,尤其是如果你需要在多个 -> 链中使用它。
+11

as->是在Clojure 1.5中添加的,因此一些较旧的入门书籍可能没有提到它,但它对于创建函数具有混合顺序的流水线肯定非常有用。

由于它们都是宏,因此这些线程宏不会对性能产生影响 - 它们在编译时会重写代码,因此在执行时你基本上正在执行与仅嵌套函数完全相同的事情。

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

为什么这不是连接函数的方法呢?

我也曾思考过同样的问题,但说实话,我使用->->>比使用as->多得多。我想这只是一个简短的问题。关于正常的线程宏的一个很酷的事是,如果一个函数只接受一个参数,你可以完全省略括号。因此

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

可以写成

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

但是,你也可以做得太过分。当我发现自己写的东西像

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

时,我会停下来散步...

+1

我总是使用它,并且没有注意到任何问题。另外,从宏的代码中可以看出,这是一个简单直接的宏,没有缺点。

0
by

as-> 很好,但其实现与 ->->> 不同,可能会导致不同的行为。 ->->> 递归地嵌套调用,而 as-> 则创建一系列 let 绑定。例如,(-> a long-running future) 转换为 (future (long-running a)),而 (as-> a $ (long-running $) (future $)) 成为 (let [$ a, $ (long-running $)] (future $))(其异步执行效果并不像看上去的那么明显)。

实际上,我没有发现这会造成问题,但这里提醒一下在特定情况下使用 as-> 的注意事项。

...