【问题标题】:-> operator in Clojure-> Clojure 中的运算符
【发布时间】:2011-09-02 22:51:58
【问题描述】:

Clojure 中的 -> 运算符(在 Clojure 中这个运算符是什么?)是否等同于 F# 中的管道运算符 |>?如果是这样,为什么需要这么复杂的宏定义,当(|>)只是定义为

let inline (|>) x f = f x

如果没有,Clojure 中是否存在 F# 的管道运算符,或者您将如何在 Clojure 中定义这样的运算符?

【问题讨论】:

    标签: f# clojure pipeline


    【解决方案1】:

    不,它们不一样。 Clojure 并不真正需要 |>,因为所有函数调用都包含在列表中,例如 (+ 1 2):没有什么魔法可以让 1 + 2 单独工作1

    -> 用于减少嵌套和简化常见模式。例如:

    (-> x (assoc :name "ted") (dissoc :size) (keys))
    

    扩展到

    (keys (dissoc (assoc x :name "ted") :size))
    

    前者通常更容易阅读,因为从概念上讲,您正在对x 执行一系列操作;前者的代码是这样“塑造”的,而后者需要一些精神上的解开才能解决。

    1 你可以编写一个宏来完成这项工作。这个想法是把你的宏包裹在你想要转换的整个源代码树周围,让它寻找|>符号;然后它可以将源转换为您想要的形状。 Hiredman 使用他的 functional 包,使以非常类似于 Haskell 的方式编写代码成为可能。

    【讨论】:

    • 我明白了。看起来线程运算符在 Clojure 中很有用,只是因为 Clojure 语法的工作方式,但在 F# 中没有用处。管道运营商反之亦然。
    • Clojure in Action 在第 50 页和第 51 页上将这些宏的名称列为“线程优先”(->) 和“线程最后”(->> ) 宏,尽管官方文档似乎没有给出它们的实际名称。
    • @ShawnHolmes clojure.org/guides/threading_macros 使用术语“线程优先”和“线程最后”。 (该页面是 2012 年以来的新页面。)
    【解决方案2】:

    它被称为“线程”运算符。出于性能原因,它被写成宏而不是普通函数,因此它可以提供一个很好的语法 - 即它在编译时应用转换。

    它比您描述的 |> 运算符更强大,因为它旨在通过多个函数传递一个值,其中每个连续值作为以下函数调用的第一个参数“插入”。这是一个有些人为的例子:

    (-> [1]
         (concat [2 3 4])
         (sum)
         ((fn [x] (+ x 100.0))))
    => 110.0
    

    如果你想定义一个与你描述的 F# 运算符完全一样的函数,你可以这样做:

    (defn |> [x f] (f x))
    
    (|> 3 inc)
    => 4
    

    不确定它到底有多大用处,但无论如何你都在那里 :-)

    最后,如果你想通过一系列函数传递一个值,你总是可以在 clojure 中执行如下操作:

    (defn pipeline [x & fns]
      ((apply comp fns) x))
    
    (pipeline 1 inc inc inc inc)
    => 5
    

    【讨论】:

    • 你为什么说->是一个“出于性能原因”的宏?这是一个宏,因为作为一个函数它不起作用。
    • 即使 "->" 是一个函数,也可以使用 (-> a #(...) #(...) #(...) ...) 获得类似的结果但使用起来会很慢而且有点难看:-)
    • (-> b (a c) quote) 如果不是宏,则永远无法评估为(quote (a b c)),并且还有其他类似的情况。 #(...) 案例将涉及手动完成 -> 已经完成的所有工作;你只会把它变成另一个版本的comp
    • 是的,我完全同意,如果不使用宏,您将无法获得完全相同的语法。我的意思是您可以获得相同的功能,尽管开销更大。无论哪种方式,我都已将句法点添加到答案中。
    【解决方案3】:

    还值得注意的是,有一个->> macro 会将表单作为最后一个参数:

    (->> a (+ 5) (let [a 5] ))
    

    Clojure 的乐趣,第 8.1 章稍微讨论了这个主题。

    【讨论】:

      【解决方案4】:

      在阅读源代码时(尤其是说话时),我总是将-> 运算符发音为“thread-first”,将->> 运算符发音为“thread-last”。

      请记住,现在有一个运算符as->,它比->->>. 更灵活,形式为:

      (as-> val name (form1 arg1 name arg2)...)
      

      val 被评估并分配给占位符符号name,用户可以将其放置在以下表格中的任何位置。我通常选择“它”作为占位符符号。我们可以像这样模仿线程优先->

      user=> (-> :a 
                 (vector 1))
      [:a 1]
      user=> (as-> :a it 
                   (vector it 1) )
      [:a 1]
      

      我们可以像这样模仿 thread-last ->>

      user=> (->> :a 
                  (vector 2))
      [2 :a]
      user=> (as-> :a it 
                   (vector 2 it) )
      [2 :a]
      

      或者,我们可以将它们组合成一个表达式:

      user=> (as-> :a it 
                   (vector it 1) 
                   (vector 2 it))
      [2 [:a 1]]
      
      user=> (as-> :a it 
                   (vector it 1) 
                   (vector 2 it) 
                   (vector "first" it "last"))
      ["first" [2 [:a 1]] "last"]
      

      我经常使用最后一种形式,所以我在the Tupelo Library 中创建了一个新的运算符it->

      (it-> 1
            (inc it)                                  ; thread-first or thread-last
            (+ it 3)                                  ; thread-first
            (/ 10 it)                                 ; thread-last
            (str "We need to order " it " items." )   ; middle of 3 arguments
      
        ;=> "We need to order 2 items." )
      

      【讨论】:

        猜你喜欢
        • 2010-12-04
        • 1970-01-01
        • 1970-01-01
        • 2016-11-17
        • 1970-01-01
        • 2012-08-03
        • 2014-01-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多