Is the -> operator in Clojure (and what is this operator called in Clojure-speak?) equivalent to the pipeline operator |> in F#? If so, why does it need such a complex macro definition, when (|>) is just defined as
let inline (|>) x f开发者_C百科 = f x
Or if not, does F#'s pipeline operator exist in Clojure, or how would you define such an operator in Clojure?
No, they are not the same. Clojure doesn't really have a need for |>
because all function calls are enclosed in lists, like (+ 1 2)
: there's no magic you could do to make 1 + 2
work in isolation.1
->
is for reducing nesting and simplifying common patterns. For example:
(-> x (assoc :name "ted") (dissoc :size) (keys))
Expands to
(keys (dissoc (assoc x :name "ted") :size))
The former is often easier to read, because conceptually you're performing a series of operations on x
; the former code is "shaped" that way, while the latter needs some mental unraveling to work out.
1 You can write a macro that sorta makes this work. The idea is to wrap your macro around the entire source tree that you want to transform, and let it look for |>
symbols; it can then transform the source into the shape you want. Hiredman has made it possible to write code in a very Haskell-looking way, with his functional package.
It's called the "thread" operator. It's written as a macro as opposed to a normal function for performance reasons and so that it can provide a nice syntax - i.e. it applies the transformation at compile time.
It's somewhat more powerful than the |> operator you describe, as it's intended to pass a value through several functions, where each successive value is "inserted" as the first parameter of the following function calls. Here's a somewhat contrived example:
(-> [1]
(concat [2 3 4])
(sum)
((fn [x] (+ x 100.0))))
=> 110.0
If you want to define a function exactly like the F# operator you have described, you can do:
(defn |> [x f] (f x))
(|> 3 inc)
=> 4
Not sure how useful that really is, but there you are anyway :-)
Finally, if you want to pass a value through a sequence of functions, you can always do something like the following in clojure:
(defn pipeline [x & fns]
((apply comp fns) x))
(pipeline 1 inc inc inc inc)
=> 5
It is also worth noting that there is a ->> macro which will thread the form as the last argument:
(->> a (+ 5) (let [a 5] ))
The Joy of Clojure, chapter 8.1 talks about this subject a bit.
When reading source code (especially when speaking), I always pronounce the ->
operator as "thread-first", and the ->>
operator as "thread-last".
Keep in mind that there is now an operator as->
which is more flexible than either ->
or ->>.
The form is:
(as-> val name (form1 arg1 name arg2)...)
The value val
is evaluated and assigned to the placeholder symbol name
, which the user can place in ANY position in the following forms. I usually choose the word "it" for the placeholder symbol. We can mimic thread-first ->
like so:
user=> (-> :a
(vector 1))
[:a 1]
user=> (as-> :a it
(vector it 1) )
[:a 1]
We can mimic thread-last ->>
like so:
user=> (->> :a
(vector 2))
[2 :a]
user=> (as-> :a it
(vector 2 it) )
[2 :a]
Or, we can combine them in a single expression:
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"]
I use this last form so much I have made a new operator it->
in the Tupelo Library:
(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." )
精彩评论