-> Clojure 中的运算符

Posted

技术标签:

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

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

let inline (|>) x f = f x

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

【问题讨论】:

【参考方案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." )

【讨论】:

以上是关于-> Clojure 中的运算符的主要内容,如果未能解决你的问题,请参考以下文章

clojure 中的分析(用于大型代码)

Clojure 中的惯用错误处理

JRuby中的Clojure STM

Clojure 中的外部联接

Clojure 中的 CSV 解析器需要避免引号中的逗号

卡在 clojure 中的泛型类类型提示