Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the argument position of split and join in clojure.string mixed up?

Tags:

string

clojure

I wanted to do this:

(-> string
    (str/split "\s")
    (modification-1)
    (modification-2)
    …
    (modification-n
    (str/join "\n"))

But no, split takes [s regex] and join takes [seperator coll].

Is there any apparent reason for this madness (read: What is the design decision behind this)?

like image 996
Profpatsch Avatar asked Aug 14 '13 14:08

Profpatsch


1 Answers

As of Clojure 1.5, you can also use one of the new threading macros.

clojure.core/as->

([expr name & forms])

Macro

Binds name to expr, evaluates the first form in the lexical context of that binding, then binds name to that result, repeating for each successive form, returning the result of the last form.

It's quite a new construct, so not sure how to use idiomatically yet, but I guess something like this would do:

(as-> "test test test" s
    (str/split s #" ")
    (modification-1 s)
    (modification-2 s)
    ...
    (modification-n s)
    (str/join "\n" s))

Edit

As for why the argument position is different, I'm in no place to say, but I think Arthur's suggestion makes sense:

  • Some functions clearly operate on collections (map, reduce, etc). These tend to consistently take the collection as the last argument, which means they work well with ->>
  • Some functions don't operate on collections and tend to take the most important argument (is that a thing?) as the first argument. For example, when using / we expect the numerator to come first. These functions work best with ->

The thing is - some functions are ambiguous. They might take a collection and produce a single value, or take a single value and produce a collection. string\split is one example (disregarding for the moment that additional confusion that a string could be thought of as both a single value or a collection). Concatenation/reducing operations will also do it - they will mess up your pipeline!

Consider, for instance:

(->> (range 1 5)
     (map inc)
     (reduce +)
     ;; at this point we have a single value and might want to...
     (- 4)
     (/ 2))
     ;; but we're threading in the last position
     ;; and unless we're very careful, we'll misread this arithmetic

In those cases, I think something like as-> is really helpful.

I think in general the guideline to use ->> when operating on collections and -> otherwise is sound - and it's just in these borderline/ambiguous cases, as-> can make the code a little neater, a little clearer.

like image 168
Daniel Neal Avatar answered Nov 15 '22 08:11

Daniel Neal