![]() |
Articles Feed |
Categories
Archives
- July 2010 (5)
- June 2010 (4)
- April 2010 (3)
- March 2010 (2)
- February 2010 (2)
- January 2010 (1)
- December 2009 (1)
- October 2009 (2)
- September 2009 (2)
- August 2009 (1)
- July 2009 (5)
- June 2009 (2)
- May 2009 (2)
- April 2009 (8)
- March 2009 (7)
- January 2009 (2)
- December 2008 (3)
- November 2008 (5)
- October 2008 (4)
- September 2008 (6)
- August 2008 (4)
- July 2008 (5)
- June 2008 (5)
- May 2008 (4)
- April 2008 (2)
- February 2008 (4)
- January 2008 (2)
- December 2007 (2)
- November 2007 (2)
- October 2007 (2)
- September 2007 (1)
- August 2007 (3)
- July 2007 (1)
- June 2007 (4)
- May 2007 (7)
- April 2007 (2)
- February 2007 (3)
- January 2007 (3)
- November 2006 (3)
- October 2006 (3)
- September 2006 (17)
- November 2004 (1)
Bowling in Clojure
by: micah | July 20th, 2009 |
I’ve been long overdue to learn a new programming language and Clojure recently caught my eye. Clojure attracted my attention because it seemed like a rich Lisp dialect where I could learn all the goodness of functional programming, and it runs on the JVM allowing me to make use of all hte Java goodness out there.
Along with a couple colleagues at 8th Light, I read Halloway’s Book. We then challenged each other to write the bowling game in Clojure so we could compare our solutions. Coincidentally, my father, Unclebob, performed the same exercise independently. Below is my solution.
First comes the tests:
- (ns micah.test.bowling (:use micah.bowling clojure.contrib.test-is))
- (deftest test-gutter-game
- (is (= 0 (score (repeat 20 0)))))
- (deftest test-one-pin-wonder
- (is (= 20 (score (repeat 20 1)))))
- (deftest test-one-spare
- (is (= 20 (score (concat (list 5 5 5) (repeat 17 0))))))
- (deftest test-one-strike
- (is (= 30 (score (cons 10 (repeat 18 1))))))
- (deftest test-perfect-game
- (is (= 300 (score (repeat 12 10)))))
- (deftest test-all-spares
- (is (= 150 (score (repeat 21 5)))))
- (deftest test-heart-breaker
- (is (= 299 (score (concat (repeat 11 10) (list 9))))))
- (run-tests)
And the production code:
- (ns micah.bowling (:use clojure.contrib.seq-utils))
- (defn score [rolls]
- (loop [score 0 rolls (vec rolls) frame 1]
- (if (> frame 10)
- score
- (cond
- (= 10 (rolls 0))
- (recur (+ score 10 (rolls 1) (rolls 2)) (subvec rolls 1) (inc frame))
- (= 10 (+ (rolls 0) (rolls 1)))
- (recur (+ score 10 (rolls 2)) (subvec rolls 2) (inc frame))
- :else
- (recur (+ score (rolls 0) (rolls 1)) (subvec rolls 2) (inc frame))))))
Thoughts
As with learing any new language, writing this first program was frustrating. Getting the environment running took a while and simple tasks, like creating a list of 20 zeros, required several minutes of research. Nonetheless, I was very pleased when it was complete.
I was quite impressed with size of the program. Compared to previous Java solutions I’ve written, this Clojure solution is about 20% the size. That’s a huge size reduction. But as I compare the solutions more, it occurrs to me that the Clojure solution is also about 20% as readable. Just look at it! You could get lost in that cond statement for eons.
Now my craftsmanship spirit was nagging me all the while I was coding, just begging me to refactor. However, I struggled to find good ways to improve the code. All my typical tricks, extract variable, extract method, didn’t feel right. My hope is that I’ll acquire new tricks to refactor in Clojure from experienced Clojure programmers, perhaps like you. Please let me know if you see a way to improve my code.

July 20th, 2009 at 08:42 PM
Hi Micah, hope you liked the book. I have written up my approach to the bowling game here: http://blog.runcoderun.com/post/145675117/tdd-in-a-functional-language-uncle-bobs-bowling.
July 25th, 2009 at 10:55 AM
First and formost let go of the state. Instead of counting to 10 frames how would you create 10 frames by applying a function to a raw list of rolls? Then if you had a list of frames what function could you apply to add them together?
cond is bread and butter in Lisp, but it doesn't have to be unreadable. Use functions instead of raw calculations (extract method becomes extract function).
In terms of TDD I would probably start with one frame at a time. Do I have functions which recognize each frame type? Do I have a function which creates a frame from a list of rolls based on type? When I have tested those functions into existance I would move on to multi-frames. I must finally test that I only score 10 frames per game.