请在 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-> 的参数顺序 — 表达式符号主体 — 适合于线程使用,但在这两种情况下,您可以直接从 -> 线程到 ->>,使线程的值位于表式的末尾。

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))










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 绑定。例如,(-> a long-running future) 转换为 (future (long-running a)),而 (as-> a $ (long-running $) (future $)) 变为 (let [$ a, $ (long-running $)] (future $))(实际上ynchronously 的运行并没有看起来那样异步)。

在实际应用中,我没有遇到这个问题,但这是对在某些情况下使用 as-> 的一种警告。

...