Hacker News new | ask | show | jobs
by junke 3647 days ago
I agree with the tooling part.

I tend to prefer the functional version to threading because (1) honestly, like fluent interfaces, it seems overused (2) function application is damn easy to read and (3) as soon as you have as many nested operations, it can and should be refactored into meaningful auxiliary functions. The first line reads as:

Multiply all ... 4 copies of ... the length of ... the string "200". So, basically, the length of that string multiplied by itself 4 times? (exponent).

The other form is more like step-by-step instructions, which is nice. However results are implicitly being passed at the first or last argument (I don't always remember which), and most everyday functions don't fit in the first/last category.

1 comments

One of the nice things about the Clojure standard library is that most everyday functions actually do fit the first/last category (by design). Functions operating on sequences (map, filter, reduce, etc) take the sequence as the last argument and are suited for use with the ->> macro, while functions operating on data structures in a non-sequence context typically take the data structure first (assoc, conj, update) and are good for ->. So you get either:

    (->> (range 10)
         (map inc)
         (filter even?)
         (take 2)) ;=> '(2 4)
or

    (-> {:body {:some {:json :data}}}
        (assoc-in [:body :some :more-json] :more-data)
        (assoc :status 200)
        (update :body json/generate-string))
    ;;=> {:body "{\"some\":{\"json\":\"data\",\"more-json\":\"more-data\"}}", :status 200}
        
It doesn't work all the time, obviously, and it can be easy to get carried away with 15 threaded map/filter/reduce calls that should be factored into separate functions, but most of the time I find it to be a nice idiom that substantially improves readability.