Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When should I use the clojure arrow macro?

In trying to stay with the functional style, I am having difficulty understanding when I should prefer:

(-> [1 2 3] reverse last)

over:

(last (reverse [1 2 3]))

When I come across both styles in a project, I find that it breaks my flow since I have to switch between thinking about function compositions and thinking about intermediate value states.

Which should I use at what times?

like image 290
ToBeReplaced Avatar asked Oct 03 '12 19:10

ToBeReplaced


People also ask

What does arrow mean in Clojure?

It has no syntactic meaning. It is just part of the symbol name. In Lisps, the arrow -> (or even just '>') is often used to imply conversion of, or casting of, one type into another.

What are Clojure macros?

Clojure has a programmatic macro system which allows the compiler to be extended by user code. Macros can be used to define syntactic constructs which would require primitives or built-in support in other languages. Many core constructs of Clojure are not, in fact, primitives, but are normal macros.


3 Answers

I mostly avoid using -> for one-argument functions; it's a lot more useful when you have multiple-argument functions, because it lets you keep each function next to its "extra args", without the focus object obscuring it.

But also, you don't have to choose one or the other extreme: one of my favorite things about -> is that it allows you to write the functions in any order at all, and you can use this freedom to write the code in whatever way you think is most readable. For example, perhaps you want to emphasize that you're taking the last of a collection, and in context the fact that it's reversed first is uninteresting:

(last (-> [1 2 3] (reverse)))

Or maybe reversing is important, and last is boring:

(-> (reverse [1 2 3]) (last))

As an aside, note that I wrote (last) and (reverse) here rather than just last and reverse even though the -> macro implicitly inserts parentheses for you if you leave them out. This was on purpose: although it's not a terribly popular style, I think it's a good idea to always use parentheses in the forms given to ->. There are several reasons for this:

  • It means you aren't surprised when (-> (blah) (fn [x] (+ 1 (* x 5)))) expands weirdly, because you are used to thinking that -> takes a form rather than a function.
  • It keeps the link between "here is an open parenthesis" and "a function is being called". It's nice to be able to grep for calls to a particular function, and it's also a nice visual hint. If you omit the parentheses, functions are called without being visually signaled.
  • It looks more regular in a multi-line -> with some unary functions and other multi-argument functions. For example:

    (-> attrs
        (update :sessions inc)
        frobnicate
        save-to-disk
        (get :uuid))
    

    Isn't it gross to have those two things in the middle offset, looking different from everything else?

The only argument I have ever seen for omitting the ()s is that it saves two characters of typing, which is not an argument at all.

like image 104
amalloy Avatar answered Oct 19 '22 19:10

amalloy


My short rule of thumb is "which one is closer to the more declarative english sentence". If you roughly translate them into descriptive sentences, i tend to choose the one that more closely states the thing being produced over the option that describes the process for creating the result. Some people will want to choose the other way. These things are always a matter of taste.

for instance:

  • (last (reverse [1 2 3])) is almost "last from reversing 1 2 3"
  • (-> [1 2 3] reverse last) is sorta like "from 1 2 3, reverse, take last"

I find the first more declarative and would choose it in this case, others will choose differently.

like image 39
Arthur Ulfeldt Avatar answered Oct 19 '22 18:10

Arthur Ulfeldt


Since they are functionally identical, the main thing to consider is which makes the code more readable and maintainable?

This is basically a judgement call.

-> is often useful in situations where:

  • You want to make it very clear that you have a chain of operations to be performed in sequence
  • You may want to insert new steps in the future. Easy - just add a new line/form.
  • Your steps have some extra arguments (it makes it very visible that these arguments are part of the step)
  • Deeply nested function calls would otherwise be confusing (although if this is really an issue, there are other ways to refactor....)

(f (g x)) style is useful in other circumstances:

  • It's often a more natural style for mathematical operations / computations since it more closely mirrors mathematical function notation
  • It is more flexible since it doesn't have the requirement that the passed-on value must always be the first (with ->) or last (with ->>) argument
  • It doesn't have the mental overhead of having to imagine the extra argument
like image 8
mikera Avatar answered Oct 19 '22 18:10

mikera