27

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 = f x

Or if not, does F#'s pipeline operator exist in Clojure, or how would you define such an operator in Clojure?

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Dax Fohl
  • 10,654
  • 6
  • 46
  • 90

4 Answers4

38

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.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • I see. Looks like thread operator is useful in Clojure just because the way Clojure syntax works but wouldn't be useful in F#. And vice versa with the pipeline operator. – Dax Fohl May 26 '11 at 21:51
  • 1
    **Clojure in Action** lists the name of these macros on page 50 and 51 as the "thread-first" (->) and "thread-last" (->>) macros, though the official documentation doesn't seem to give them actual names. – Shawn Holmes Jan 02 '12 at 03:29
  • @ShawnHolmes https://clojure.org/guides/threading_macros uses the terms "thread-first" and "thread-last". (The page is new since 2012.) – Biped Phill Feb 21 '20 at 00:59
13

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
mikera
  • 105,238
  • 25
  • 256
  • 415
  • 2
    Why do you say `->` is a macro "for performance reasons"? It's a macro because as a function it wouldn't work. – amalloy Aug 18 '11 at 18:41
  • 1
    You could achieve a similar result with (-> a #(...) #(...) #(...) ...) even if "->" was a function but it would be both slow and somewhat ugly to use :-) – mikera Aug 18 '11 at 19:07
  • 1
    `(-> b (a c) quote)` can never evaluate to `(quote (a b c))` if it's not a macro, and there are other similar cases. The `#(...)` case would involve doing all the work that `->` already does, manually instead; you'd just be making it into another version of `comp`. – amalloy Aug 19 '11 at 01:13
  • 1
    yeah I completely agree, you can't get exactly the same syntax without using a macro. My point was just that you *can* get the same functionality, albeit with more overhead. Either way I've added the syntactic point into the answer. – mikera Aug 19 '11 at 10:43
12

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.

Julien Chastang
  • 17,592
  • 12
  • 63
  • 89
11

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." )
Alan Thompson
  • 29,276
  • 6
  • 41
  • 48