请在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->的参数顺序——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))
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

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

as-> 非常好,但它的实现与 ->->> 不同,可能会产生不同的行为。《->》 和 《->->》 递归嵌套调用,而 《as->》 则创建一系列的 let 绑定。例如,(-> (一个长时间运行的未来)) 转换为 (future (long-running a)),而 (as-> (a $ (long-running $) (future $)) 变为 (let [a $, (long-running $)] (future $))(它看起来不是那么异步)

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

...