【问题标题】:Clojure thread-first macro (->) vs thread-last macro (->>) What are the practical differences?Clojure thread-first 宏 (->) 与 thread-last 宏 (->>) 有什么实际区别?
【发布时间】:2021-01-08 13:29:36
【问题描述】:

我仍然不完全理解这两个 Clojure 箭头宏 thread-first -> 和 thread-last 宏 ->> 之间的区别。 在阅读https://clojure.org/guides/threading_macros 时,我了解到线程优先 -> 是针对单个对象的嵌套操作的替代表达式,每个数据步都使用该对象作为输入参数,执行独立的操作。

(defn transformation [object]
  (transform2 (transform1 object)))

变成

(defn transformation [object]
  (-> object
      (transform1)      ;; object as implicit argument
      (transform2)))    ;; object as implicit argument 

当使用threat-last ->> 运算符时,每个转换都使用前面转换的输出作为隐式参数:

(defn transformation [object]
  (->> object
       (transform1)      ;; object as implicit argument
       (transform2)))    ;; (transform1 object) as implicit argument

这些差异的实际含义是什么?我知道将威胁 -first -> 用于地图和字典上的操作是有意义的,其中每个转换都会创建原始实例的新副本,必须为下一次操作提供该副本。 但是,在许多情况下,这两个运算符的行为实际上是相同的:

(->> [2 5 4 1 3 6] (reverse) (rest) (sort) (count)) ;; => 5
(->  [2 5 4 1 3 6] (reverse) (rest) (sort) (count)) ;; => 5

【问题讨论】:

  • Stuart Sierra 有一篇名为 threading with style 的帖子,对线程宏的使用有很好的指导。

标签: clojure


【解决方案1】:

实际的区别在于宏将变量(以及后续结果)“插入”到表达式中:

(ns so.example)

(defn example-1 [s]
  (-> s
      (str "foo")))

(defn example-2 [s]
  (->> s
      (str "foo")))

(example-1 "bar")
;=> "barfoo"

(example-2 "bar")
;=> "foobar"

所以(-> "bar" (str "foo"))(str "bar" "foo") 相同,(->> "bar" (str "foo"))(str "foo" "bar") 相同。使用一元函数 ->->> 做同样的事情。

当您需要更大的灵活性来确定这些结果的插入位置时,您可以使用as->

(ns so.example)

(defn example-3 [s]
  (as-> s v
      (str "foo" v "foo")))

(example-3 "bar")
;=> "foobarfoo"

【讨论】:

  • 好吧,我的两个例子表现相同的问题是它们只是一元表达式的嵌套链:(count (sort (rest (reverse [2 5 4 1 3 6])))) ?
  • @GitCrush 是的,我会这么说。但请等待其他答案;那里有很多优秀的 Clojurists,我根本不与他们相比;)
  • @GitCrush 或者换句话说:对于单参数函数,“threading first”和“-last”的“first”和“last”参数是相同的参数位置。
  • 请注意:as-> 旨在在 -> 链中使用,而不是独立使用 - 这就是为什么它的 value 参数是第一个而它的 name 参数是第二个: (-> s (str "后缀") (as-> x (str "{" x "}"))
  • @customcommander 您的示例太小/太简单,无法真正说明 as-> 存在的原因。您的三个示例函数真的太小/太简单了,甚至不需要-> 或->>。 as-> 背后的想法是,它可以在由 -> 线程化的函数管道的中间使用,以便将值放在 as-> 的第一个参数中,并提供将该值绑定到的符号,仅针对管道中的一个插槽,您需要在多个位置或在非第一个(或非最后一个)位置使用该值。很难在简短的评论中解释,但希望能澄清预期的用途?
【解决方案2】:

跟进as-> 作为答案纯粹是因为我无法在评论中格式化代码。

这是我们工作代码库中as-> 的用法:

                    (-> date
                        (.getTime)
                        (as-> millis
                          (/ (- (.getTime (java.util.Date.)) millis)
                             1000 60 60 24))
                        (long)
                        (min days))

该计算可以展开,->> 用于将线程值放在 - 表达式的末尾,但可能更难阅读。也可以以这样的方式展开它,仅-> 就足够了(通过否定线程值,然后将其添加到(.getTime (java.util.Date.))),但我认为这会使其更难阅读。

【讨论】:

    【解决方案3】:

    我认为您对-> 的工作方式存在误解。

    你说

    1. -> 是针对单个对象的嵌套操作的替代表达式,每个数据步都使用该对象作为输入参数,执行独立的操作。

    然后是你说的->>

    1. 使用threat-last ->> 运算符时,每个转换都使用前面转换的输出作为隐式参数

    但陈述 1 不正确,陈述 2 对 ->->> 都是正确的。 这很容易测试,如下所示:

    cljs.user=> (-> [2 5 4 1 3 6] reverse rest)
    (3 1 4 5 2)
    cljs.user=> (-> [2 5 4 1 3 6] rest reverse) 
    (6 3 1 4 5)
    

    如果您将 sort 添加到调用链的末尾,就像在您的示例中一样,您不会注意到这种差异,因为两个结果都会被排序。

    就像cfrik 说的,当你只传递一个参数给一个函数时,那么第一个参数和最后一个参数是相同的(因为只有一个),所以这就是为什么当所有函数都传递时很容易混淆在您的调用链中只接受一个参数,在您使用 countsortrestreverse 的示例中就是这种情况。

    您可能从https://clojure.org/guides/threading_macros 的文档中遗漏的另一件事是,许多使用序列的函数,如filtermapreduce,将序列作为最后一个参数作为约定,这样就可以使用->> 链接对它们的调用,例如

    (->> (range 10) 
         (filter even?)
         (map #(+ 3 %))
         (reduce +))
    

    变成了

    (reduce + (map #(+ 3 %) (filter even? (range 10))))
    

    -> 更适合assocupdate 之类的函数,它们(如您所说)处理单个对象并将对象作为第一个参数(并将该对象上的“转换/更新”作为其余的参数),那么您可以执行以下操作

    (-> person
        (assoc :hair-color :gray)
        (update :age inc))
    

    变成了

        (update (assoc person :hair-color :gray) :age inc)
    

    为了更好地理解宏的工作原理,请尝试使用macroexpand-1,像这样

    user> (macroexpand-1 '(->> (range 10) 
                          (filter even?)
                          (map #(+ 3 %))
                          (reduce +)))
    (reduce + (map (fn* [p1__21582#] (+ 3 p1__21582#)) (filter even? (range 10))))
    

    参数macroexpand-1应该是函数调用的引用版本,结果将是扩展的函数调用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-05-22
      • 2012-01-22
      • 2012-01-24
      • 2019-09-10
      • 2014-09-02
      • 2023-03-28
      相关资源
      最近更新 更多