Learning to switch from (a) common lisp to idiomatic clojure
Sometimes you just want to show your new toys to your friends. This particular time, the new toy was Clojure. And the friend in question happened to have a (albeit reduced) Lisp background. So he was about to start a new function to find out if some arguments were all in a specific list, his train of thought was something like this:
Return true if some of those arguments are not in the [:read :written :deleted] sequence.
Having just read about the arrays being useful as functions, he tried to exploit that. He skimmed the documentation on the clojure site, he found out there is no contains nor a is-in, but there was a some. so he wrote:
(defn only-right-args? [args] "Only the keywords :read :written and :deleted will be valid." (not (some (fn [x] (not ([:read :written :deleted] x)) args))))
Now this is interesting. We see a literal approach here. Even without evaluating (and without spotting the obvious reason for it to fail), I introduced him to the anonymous functions syntactic sugar. And so he wrote:
(defn only-right-args? [args] "Only the keywords :read :written and :deleted will be valid." (not (some #(not ([:read :written :deleted] %)) args)))
Not bad. But reading “not-some-not” isn’t pleasant. So I told him about every?, that he somehow missed in the documentation. And things got a bit cleaner:
(defn only-right-args? [args] "Only the keywords :read :written and :deleted will be valid." (every? #([:read :written :deleted] %) args))
But in terms of redundancy, I explained that he was making an anonymous function with one argument with the single purpose of applying another function of one argument, and that – being in a language with functions as first-class-citizens – he might as well pass that function. He found it weird (afterall, seeing an array isn’t immediately obvious that it’s also a function), but after a (very short) while he started preferring this:
(defn only-right-args? [args] "Only the keywords :read :written and :deleted will be valid." (every? [:read :written :deleted] args))
Great – much simpler than the first attempt! Now it’s time to feed this to the repl, and get – suprise! – disappointed… That’s right, an array can be used as a function, but one that receives an index as argument. After reading about this, he thought of using a map, but since maps would need to have values related to the keys, he would have to do something like {:read true, :written true, :deleted true}, or even (zipmap [:read :written :deleted] [true true true]), but neither was simple enough. And at this point I mentioned the concept of Sets. So he quickly fixed his code to this:
(defn only-right-args? [args] "Only the keywords :read :written and :deleted will be valid." (every? #{:read :written :deleted} args))
A quick test showed him that this code worked, as well as being easy on the eye:
user> (only-right-args? [:read :written]) true user> (only-right-args? [:read :blacksheep :written]) false
This was fun. Some valuable lessons were learnt, specially on how it’s generally better to give in, when talking about Clojure’s idiomatic forms taking over the more traditional “show-me-how-to-open-and-close-parenthesis-and-ill-code-whatever-i-need-to-do-it” philosophy!
Now, a question for you, gentle reader – What would you improve in the latest function definition? Is there a better/faster/cleaner way to do it in Clojure?


4 Comments
Leave a Comment