请分享您的想法,参与 2024 年 Clojure 调查问卷!

欢迎!请参阅 关于 页面,获取更多关于如何使用本站的信息。

+4
Clojure

我断断续续地玩了几年 Clojure,也读过几本书,但前几天我再次阅读了链式宏指南,终于注意到了 as->。也许我只是瞎了眼,但我发誓我从来没在任何例子中看到过这个用法,也没在任何书中被突出显示过。与使用更不灵活的链式宏相比,使用它会对性能产生重大影响吗?

我总是喜欢通过将东西放入 -> 或 ->> 来构建数据处理管道的经历(能够通过删除一行注释来去掉测试步骤真是太棒了!),但我却陷入了一个泥潭,需要让所有我想要与 -> 或 ->> 玩得好的函数。我承认我曾把一个函数包装起来以改变它的参数顺序。我一直在享受 as->,感觉肯定有什么缺点是我没有注意到。为什么这不是链接函数的 正确方式 呢?

5 答案

+13

我惊讶没有人提到 as-> 是为在 -> 中使用而设计的(所有链式宏都是为在 -> 中使用而设计的)。在阅读其他人的回答时,这是我期待的

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

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

正如您所看到的,as-> 的参数顺序——expr 符号 body——非常适合链式,但在这两种情况下,您可以直接从 -> 缝入 ->>,以在表达式的末尾获得链式值。

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 ...]
  (讨厌的其他 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-> 应该注意的。

...