First of all, I've never been really upset about parenthesis. In fact, I've been doing a bit of Java these days and I don't see much difference between
verify(foo).bar(eq(100), any(Baz.class), eq("Cat"))
and (-> foo verify (.bar (eq 100) (any Baz) (eq "Cat)))
. By my count it's the same number of parenthesis. Where they're located moves around a bit, but I don't consider that to be a good or a bad thing.People also like to bring up examples like this:
(apply merge-with +Stefan Tilkov addressed this in a previous blog entry, and (in the comments of Stefan's entry) Rich Hickey points out that you can use a few different versions if you prefer to lay your code out in a different manner. Below are two alternative solutions Rich provided.
(pmap count-lines
(partition-all *batch-size*
(line-seq (reader filename)))))
; The same code in a pipelined Clojure style:I've felt the same way for awhile. You can write Cobol in any language. The real question is, are you up for learning how to solve problems elegantly and idiomatically in a new language. If you're willing to invest the time, you'll be able to find out for yourself if it feels natural to write idiomatic Clojure code.
(->> (line-seq (reader filename))
(partition-all *batch-size*)
(pmap count-lines)
(apply merge-with +))
; and in a step-wise Clojure style with labeled interim results (a la Adrian’s comment):
(let [lines (line-seq (reader filename))
processed-data (pmap count-lines
(partition-all *batch-size* lines))]
(apply merge-with + processed-data))
That was my answer, until today. While working on some Java code I stumbled on the following Java method.
public void onFill(int fillQty) {This is a simple Java method that is decrementing the outstanding quantity state of an order by the amount of the order that just been filled. While reading the line I couldn't help but feel like there should be a more elegant way to express the logic. You want to set the outstanding quantity state to the current outstanding quantity minus what's just been filled, but you also never want the outstanding quantity to go below zero. I read from left to right, and I really wanted a way to express this logic in a way that followed the left to right pattern.
this.qty = Math.max(this.qty - fillQty, 0);
}
In Clojure, this is easy to do:
(swap! qty #(-> % (- fill-qty) (Math/max 0)))For readers who are less familiar with Clojure's dispatch reader macro, the above example can also be written as:
(swap! qty (fn [current-qty] (-> current-qty (- fill-qty) (Math/max 0))))In the example above swap! is setting the qty state with the return value of the function.
If you're really new to Clojure, that might still be too much to take, so we can reduce the example and remove the state setting. Here's the version in Java that ignores setting state.
Math.max(this.qty - fillQty, 0);The example below is a logical equivalent in Clojure.
(-> qty (- fill-qty) (Math/max 0))When reading the above Java example I'm forced to put Math.max on my mental stack, evaluate this.qty - fillQty, and then mentally evaluate the method I put on the stack with my new result and the additional args. This isn't rocket science, but it's also not how I naturally read (left to right). On the other hand, when I read the Clojure version I think - take the current quantity, subtract the fill quantity, then take the max of that and zero. The code reads in small, logical chucks that are easy for me to digest.
Obviously, the Java code can also be rewritten in a few other ways. Here's an example of Java code that reads left to right and top to bottom.
public void onFill(int fillQty) {And, I can do something similar in Clojure, if I want.
this.qty -= fillQty
this.qty = Math.max(this.qty, 0);
}
(swap! qty #(- % fill-qty))While it's possible to write Clojure similar to the above example, it's much more likely that you'd use a let statement if you wanted to break up the two operations.
(swap! qty #(Math/max % 0))
(defn update-qty [current fill-qty]The above example is probably about equivalent to the following Java snippet.
(let [total-qty (- current fill-qty)]
(Math/max total-qty 0)))
(swap! qty update-qty fill-qty)
public void onFill(int fillQty) {So, I can write code that is similar to my options in Java, but I'm still left wanting a Java version that is similar to this Clojure example:
int newTotalQty = this.qty - fillQty
this.qty = Math.max(newTotalQty, 0);
}
(swap! qty #(-> % (- fill-qty) (Math/max 0)))The only thing that springs to mind is some type of fluent interface that allows me to say
this.qty = this.qty.minus(fillQty).maxOfIdentityOrZero()
, but I can't think of a realistic way to create that API without quite a bit of infrastructure code (including my own Integer class).(note, you could extend Integer in a language with open classes, but that's outside the scope of this discussion)
The last Clojure example is definitely the version of the code I would prefer. My preference is based on the way the code reads in concise, logical chunks from left to write. I don't have to solve inside out like the original java version forces me to, and I don't have to split my work across two lines.
I'm sure there are situations where Java allowed me to create an elegant solution that wouldn't have been possible in Clojure. This entry isn't designed to send a "Clojure is better than Java" message. I don't believe that. However, before today I've held the opinion that you can write Clojure that logically breaks up problems in ways very similar to what you do using Java. However, I've now also expanded my opinion to include the fact that in certain situations Clojure can also allow me to solve problems in a way that I find superior to my options within Java.
And, yes, after a bit of time getting used to Lisp syntax, it definitely does feel perfectly natural to me when I'm developing using Clojure.
Try ruby ;)
ReplyDeletedef on_fill(fill_quantity)
@quantity = [@quantity - fill_quantity, 0].max
end
This comment has been removed by the author.
ReplyDeletePlease ignore my previous comment. It's too early in the morning here for me to think properly. Great post - thanks for sharingf :-)
ReplyDeleteLove this post, especially how you drill into all the different ways to express the quantity-setting example. Thanks!
ReplyDeleteI'm impressed with Clojure, and have written some small things in it, but have yet to really dig in and write something serious in Clojure. But I've been off and on programming in Scheme, CL, and a couple of obscure Lisp dialects for well over 10 years now. I don't find the prefix notation all that unnatural. For some things it is less natural, but for others it is more. Basically, you get used to it. I do find the Clojure pipelining operators hard to read but I imagine that's also something you get used to.
ReplyDeleteI do find a reasonable amount of Lisp unnecessarily hard to read, often for the same reason I find a lot of code written in infix notation hard to read. People don't bundle things up and give them names to the degree I would like. I think that is a larger factor in readability than almost anything else (assuming you aren't doing anything insane.)
Anyway, nice post. Thanks!
I don't think of "taking the maximum of new availabity and 0" (whatever permutation). I think "reduce available quantity, but not below zero".
ReplyDeletedef fill_with amount
@available -= amount
if @available < 0
@available = 0
end
end
When I do that, I suddenly wonder where the overflow goes.