There's a simple rule I use: -> is for maps and ->> is for collections.
Functions that take a map should take it as first argument, functions that take collections should take the collection as last argument. This is how the clojure core functions work, eg. -> `assoc`, `conj`, `dissoc` vs ->> `map`, `filter`, `reduce`, `some`.
If you follow this rule in your code, threading is nice and looks good. If you want to force different argument position, use a lambda, like so (->> coll (#(my-fn %))).
However, I recommend against it. Use a binding and then use the other threading macro if necessary.
I've found -<> and -<>> very useful- they by default put things in first and last place respectively, but let you override with <>:
(-<> x (foo bar) (map baz <>))
Found here: https://github.com/rplevy/swiss-arrows
In that case, why not go all the way and use the the exciting new ->->><?as->cond->! macro demonstrated in Every Clojure Talk Ever? ;-)
This is the biggest criticism of clojure I agree with the author on. Trying to compose threading macros is extremely annoying even though it’s not that difficult to refactor. Somewhere in the thread, you’ll want to put the return value of the previous function in a different argument place. I am not sure the reasoning behind designing the macros this way. With how well the language is designed, this annoyance has always seemed out of place and somewhat of an after thought. I made my own macro [0] as an experiment to circumvent this annoyance... which was an interesting experience (both writing and using the macro). Still, I don’t think it’s a good enough solution so I’m still trying to think of better ways to implement thread macros in clojure because I love the idea.
[0] https://github.com/cj-price/pipe.dream