Why is not clojure more data-last oriented?

Hello,

One of my first interactions with FP was on javascript through the ramda library. Ramda is a library focused on FP and all it’s methods take the data as last arguments. This is extremely useful for function composition, piping, currying and so on.
One of the first things that raised my alarms from clojure was that some functions works this way while other functions does not. For me this is an inconsistency and it is confusing.
Because of this instead of succint and elegant things like this:

(condp get a 
       :a (str ":a exist")
       :b (str ":b exists"))
     
(condp get a 
       :a :>> #(str ":a exist and contains " %))

Im forced to do this instead

(condp #(%1 %2) a 
       :a (str ":a exist")
       :b (str ":b exists"))
     
(condp #(%1 %2) a 
       :a :>> #(str ":a exist and contains " %))

Why? Because get takes the collection as first argument, instead of last and reversing the arguments produces nil, hence nothing matches. This is a simple case, more complex functions will be way worse.
At first I thought that this may be because clojurians tends to prefer macros over function composition and in that case this may make sense because the macro -> but this does not makes much sense knowing that function that operates on collections does not work this way and that the macro ->> exists, so I’m again at the start point.

If this were Javascript the answer will be easy to me: years of bad decisions. But I know that Rich Hickey thought about clojure for years before starting to implement it, so this leaves me guessing if there is another reason I can’t figure out.

so, why ?

1 Like

See: https://groups.google.com/forum/#!topic/clojure/iyyNyWs53dc

2 Likes

Link given by @rauh explains it all.

That said, I just wanted to point out, you do noot need to call str. str is only useful if you want to concatenate strings, otherwise this is enough:

(condp #(%1 %2) a 
       :a ":a exist"
       :b ":b exists")

Also, there will never be an ideal ordering, there’s always a use case that isn’t ideal. So Rich is correct in saying, you always end up needing #() eventually. For example, what if you wanted to do the opposite:

(condp get a
  {:a 2 :b 3} :>> identity
  {:c 4 :d 5} :>> identity)

In addition to the discussion linked by @rauh, I’d also observe that the more idiomatic way of basing a conditional on the presence of various keys would be:

(cond (:a a) ":a exist" 
      (:b a) ":b exists")

(cond->> a ; threads a as last argument of (str ...) call
         (:a a) (str ":a exist and contains "))

At work we have a couple of extensions to the threading macros, so we could write that as:

(condp->> a
          :a (str ":a exist and contains "))

The difference between our condp->> and the built-in cond->> is that our macro threads the expression into the predicate as well as the result.

2 Likes

And a lot of unnecessary repetition. Your condp macro is exactly what I find more useful and idiomatic. You should not be so convinced of your idiomatic way if you have a macro like that :smile:

That is true, but that is also true for the current situation. I mean, having the data last will lead to problems on the opposite situations whee it is not at the current state, so the inconvenience will be more or less the same. However the signature of functions would be more predictable and consistent

I think our macro is idiomatic – that’s why I wrote it and that’s why we use it. “Idiomatic” is not the same as “must be in clojure.core” – part of Clojure’s essence is a small, powerful language, that you can build everything you need on top of, rather than a “kitchen sink” of a language (which is what most of the others out there tend to be).

2 Likes

Absolutely true. Simplicity and small number of things to learn is always a good thing.

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.