Uncle Bob, JSPS: Learning Clojure 22
(JSPS: Just Some Poor Schmuck)
I’m trying to learn Clojure, and I’m finding the effort challenging. Perhaps you can help me.
Programming in Clojure (A derivative of Lisp) requires a certain “inside-out” thinking. I’m finding the combination of the shift in thought process and TDD quite difficult to resolve. So I thought I’d ask for your help.
Below is the Bowling Game written in Clojure complete with unit tests. I’m not at all pleased with the result. The tests, especially, look bizzare and backwards to me.
Please post your own solutions to this so that we can all compare and contrast them.
Tests
(ns bowling-game)
(use 'clojure.contrib.test-is)
(use 'bowling-game)
(defn roll-list [game list]
(if (empty? list)
game
(roll-list (roll game (first list)) (rest list))
))
(defn roll-many [game n pins]
(loop [i n g game]
(if (zero? i)
g
(recur (dec i) (roll g pins)))))
(defn gutter-game [game] (roll-many game 20 0))
(deftest can-create-game
(is (not (nil? (new-game)))))
(deftest gutter-game-should-score-0
(is (= 0 (score (gutter-game (new-game))))))
(deftest all-ones-should-score-20
(is (= 20 (score (roll-many (new-game) 20 1)))))
(deftest one-spare
(is (= 16 (score
(roll-many
(roll-list (new-game) [5 5 3])
17 0)))))
(deftest one_strike
(is (= 24 (score
(roll-many
(roll-list (new-game) [10 3 4])
16 0)))))
(deftest perfect-game
(is (= 300 (score (roll-many (new-game) 12 10)))))
(run-tests 'bowling-game)
Code
(ns bowling-game)
(defn new-game [] [])
(defn next-two-balls [rolls]
(+ (rolls 0) (rolls 1)))
(defn score-strike [rolls]
[1 (+ 10 (+ (rolls 1) (rolls 2)))])
(defn score-spare [rolls]
[2 (+ 10 (rolls 2))])
(defn score-no-mark [rolls]
[2 (next-two-balls rolls)])
(defn score-next-frame [rolls]
(if (= 10 (first rolls))
(score-strike rolls)
(if (= 10 (next-two-balls rolls))
(score-spare rolls)
(score-no-mark rolls))))
(defn score [game]
(loop [frame 1 rolls game score 0]
(if (> frame 10)
score
(let [frame-score (score-next-frame rolls)]
(recur (inc frame) (subvec rolls (frame-score 0)) (+ score (frame-score 1)))))))
(defn roll [game pins]
(conj game pins))

I think it would probably be more beneficial to focus on clojure tha language and not get hung up on the testing aspect of it at first. You may hate this advise but it will ultimately make more sense in the long run. Functional programming is different and you shouldfocus on that. Testing will follow and make much more sense in time.
One thing you could do is use the let construct more in the tests to name your return values you are asserting against.
After thinking about my previous comment for a while I realize that my advice on let would just introduce clutter unless you extract the actual contents of the original tests to a separate function. Doing that might be overkill.
The test-is library IMHO would benefit from xUnit-like functions/macros: is-equal, is-not etc. These are of course trivial to write, but it’s always better to have them in the framework. I quess test-is doesn’t have those because it tries to be as minimal as possible (?)
I am also having a bit of a challenge w/ Clojure. I will say that the prag prog Programming Clojure book has been a useful assistant. I have not been able to vocalize specific challenge with it but I believe ‘inside out thinking’ is pretty accurate.
Test first is simply the way I think and I perhaps have a more challenging time expressing the idealized API of the code under test. This could simply be a language newb issue, or it could be something else. It could easily be that I can’t really see the cross programming paradigm fp/oo first principles…
I think that is likely it; I am having trouble sorting out is precisely what ‘first principles’ are applicable in non-oo languages. These principles are even more difficult to pull out in hybridized oo/ fp languages. In a way I have had an easier time in Haskell than in Scala, F# and Clojure.
DRY, small, intention revealing, factored – more cross paradigm rules???
Bob, it’s great to hear you’re interested in Clojure! I hope you enjoy your journey.
That “inside-out” feeling you’re having is probably the cause of your unease about the tests you’re writing. Clojure, like Lisps in general, is more about the application of functions to the results of other applications of functions: (roll-list (roll-many (new game).... You may find that in time you get used to these nested parenthetical function applications, and it becomes much faster to parse them mentally. But here are some thoughts on making the test definitions less bizarre.
You’re doing what I would do in your tests, creating helpful functions like roll-list, roll-many, and gutter-game so actual deftest expressions are as terse as possible.
You could experiment with doing more “setup” work in the defns, or even just making them value definitions:
(def gutter-game-score (score (roll-many (new-game) 20 0)))
(deftest gutter-game-should-score-0
(is (= 0 gutter-game-score)))
This might be appropriate if you can’t think of tests that require you to pass an argument other than a new game, and if you’re only focused on scores produced by your code. I think (= 0 gutter-game-score) is a very tidy assertion.
As Auramo suggested, you can use let to define values in your tests, to make the actual assertion terser:
(deftest one-spare
(let [one-spare-score (score (roll-many
(roll-list (new-game) [5 5 3]) 17 0))]
(is (= 16 one-spare-score))))
It’s also likely that test frameworks in Clojure will improve and develop: it’s still early in the game.
One other tip for you. I noticed you named ‘one-spare’ with a hyphen and ‘one_strike’ with an underscore. Both are legal names, but you’ll save yourself some trouble deciding on one standard. I mixed the two symbols liberally early in my FP days, and misery briefly ensued as I confronted errors about missing functions.
Happy coding!
Uncle Bob, I would just make two comments: one, the first two functions in your test code look like classic reduce and map functions, respectively. E.g., something roughly like this: (defn roll-list [game list] (reduce roll (conj game list))) and (defn roll-many [game n pins] (nth (iterate #(roll % pins) game) n))
Have you considered using Clojure’s -> macro? It lets you sequence together calls in a way which lets you get rid of the deep, back-to-front function call nesting.
Eg:
(deftest one-spare (is (= 16 (-> (new-game) (roll-list [5 5 3]) (roll-many 17 0) score))))A couple guys at 8th Light, including myself, went through this same exercise. My solution is posted at http://blog.8thlight.com/articles/2009/7/20/bowling-in-clojure
I look forward to learning ways to write better clojure.
Non-OO programming can get “inside-out” if you’re too deeply embedded in OO thinking. Your first warning sign is learning Smalltalk.
As for first principles, look for the ground rules for ADT’s. In traditional message-sending-model OO such as Smalltalk and Java, ADT’s are types in hierarchies. They get decorated with methods and their instances send each other messages to get stuff done. In Scheme or Lisp, you define some list structure, and create the ADT by writing the functions that interpret it.
Then there is the multiple-dispatch we find in CLOS, and that we find in Clojure too. Message-sending OO is single-dispatch, meaning that each message has a single receiver, used to dispatch to actual methods. Multiple-dispatch means the method is resolved based on more instances. This step from single to multiple basically inverts the whole way you build abstractions. Instead of just thinking about your instances in a hierarchy, you must think of your methods in a hierarchy as well, and it runs off into multiple dimensions very quickly. This means Lispers have infinitely more potential for messing up, but I think they like to stick to the Lisp/Scheme way outlined above.
It will be interesting to see which best practices surface in the Clojure community over the next few years, it should be a common ground for many different schools of thought.
Hi Uncle Bob, glad to see you are looking at Clojure. I am working on my solution over at http://github.com/stuarthalloway/clojure-bowling, and I will post in more detail when I am happy with it.
There’s already some pretty interesting stuff there—most importantly, thinking in terms of a sequence of scores instead of just a total.
First, I agree with Michael Harrison that the test functions look better if you define values to test and then use them in the test function. I would likely do it this way:
This can be done with the other game tests as well, and makes it easier if you want to test different functions over the same game.
roll-manywould benefit from the built-inrepeatorreplicatefunctions:reducewas mentioned earlier as a way to build up a result from a collection. I used it to simplifyroll-list, since each call torolltakes a game and a move to make a new game. It’s much nicer than a manualloopandrecurwhen applicable.In fact, I would be tempted to add this functionality into
new-game. The zero argument case would return the empty vector as normal, and the single argument case would act asroll-liston a new game:This would subsume some
roll-listcalls, thoughroll-listwould still be needed for defining the single spare and strike games. You could create the game from any sequence, so both(new-game '(1 2 3))and(new-game [1 2 3])would work.(Note: the initial
declareis needed since roll is defined later. Movingrollearlier would eliminate the need, but havingnew-gameprecede it seems nicer.)I’m still working on the main body of the code, but here are a few other changes I like. In
score, instead of usingletto hold the result of ascore-next-framecall and pick out the pieces later, we can use destructuring to directly pull out and name them.frame-scorebecomes[rolls-used single-score], and we can userolls-usedandsingle-scoreinstead of(frame-score 0), etc. Here’s my resultingscore:(defn score [game] (loop [frame 1 rolls game score 0] (if (> frame 10) score (let [[rolls-used single-score] (score-next-frame rolls)] (recur (inc frame) (subvec rolls rolls-used) (+ score single-score))))))And lastly, I like the style of using
condfor multiple condition statements, so I replaced the nestedifs inscore-next-frame. I think this is up to the individual programmer, but having all the cases lined up looks nicer to me.(defn score-next-frame [rolls] (cond (= 10 (first rolls)) (score-strike rolls) (= 10 (next-two-balls rolls)) (score-spare rolls) :else (score-no-mark rolls)))Ok, I am up to a solution I like reasonably well at http://github.com/stuarthalloway/clojure-bowling. Here’s the key bits:
Any chance you could syntax-highlight your code? My brain simply shuts down when I try to read monochrome code.
I like Stuart’s approach. I was keeping the game in a vector as the original code did because it most easily supported the requirements for the
rollfunction. It was causing problems in my attempts at simplifying the main body of code though. Switching over to sequences makes a lot of sense forscore.Out of curiosity, what is the idiomatic way of adding a single element to the end of a sequence? That would make it easy to implement
rolland use sequences everywhere.Just use recursion, and the code writes itself…
You can improve efficiency by enforcing an input of vectors and using subvec, or you can keep the generality of sequences but change the first test to something like (= (count (take game 3)) 3), but really, why bother doing anything at the expense of clarity for a problem where the inputs are so small that efficiency is a non-issue.
BTW, the best book for learning how to think recursively is How To Design Programs, available for free at htdp.org.
Whoops, I guess I don’t understand bowling scoring as well as I thought. Now that I’ve read up a bit more on bowling scoring, I see that if you get down to three rolls, (say 10, 7, 2) it must be scored differently depending on whether it is a strike in the 9th frame followed by 2 balls in the 10th frame or just three balls from the 10th frame (in the first scenario, the second and third ball get counted twice). So it looks like you really do need to assemble it into a list of frame-by-frame scores.
Unfortunately, I misused the “is” macro (forgot to put =), which prevented me from catching my incorrect tests.
The result is similar in spirit to Stuart’s code, but a bit more compact without as many helper functions.
Sorry for the confusing choice of variable name. Should be “game” not “games” in:
(defn score [game] (sum (take 10 (frame-scores game))))While Mark’s solution is cool (and very short) I believe that decomposing into named helper functions better blends TDD and functional style. I have written up my approach over on the RunCodeRun blog.
Another variation on Stuart’s approach here, but modifying slightly to make the notion of “types of rolls” into a first-class concept:
A caveat:The guy I worked with on this and I are from Boston, so we scored this according to candle-pin rules. That may make this less useful for some folks.
A buddy and I were talking through designs for this, and as we talked about scoring frames, we realized that there were three cases:
1) Strike – score the first ball, plus the next two ‘bonus’ balls, then remove the first ball (the strike) from the list, then continue scoring.
2) Spare – score the first two balls, plus the next ‘bonus’ ball, then remove the first two balls (the spare) from the list, then continue scoring.
3) No-mark – score all three balls in the frame, then remove them from the list, then continue scoring. (Remember: candlepin!)
In all these cases you want to score three balls, the only difference was in how many balls you remove from the list before you continue scoring (i.e., how many balls were in the frame you are removing from the yet-to-be-scored list). So this is what we came up with:
http://github.com/zdsbs/candlepin-bowling/tree/master
(ns scorecard) (defn third [rolls] (first (next (next rolls)))) (defn first-two [rolls] (+ (first rolls) (second rolls))) (defn strike? [rolls] (= 10 (first rolls))) (defn spare? [rolls] (= 10 (first-two rolls))) (defn remove-strike [rolls] (rest rolls)) (defn remove-spare [rolls] (rest (rest rolls))) (defn remove-normal-frame [rolls] (rest (rest (rest rolls)))) (defn remove-frame [rolls] (if (strike? rolls) (remove-strike rolls) (if (spare? rolls) (remove-spare rolls) (remove-normal-frame rolls)))) (defn score [input-rolls] (loop [rolls input-rolls score 0 frame-counter 0] (if (or (empty? rolls) (= 10 frame-counter)) score (recur (remove-frame rolls) (+ score (first rolls) (second rolls) (third rolls)) (inc frame-counter)))))And the tests:
(ns scorecard-test (:use clojure.contrib.test-is scorecard)) (deftest test-third (is (= 3 (third [1 2 3 4])))) (deftest test-first-two (is (= 3 (first-two [1 2 3])))) (deftest test-remove-frame (is (= [3 4 5] (remove-frame [0 1 2 3 4 5]))) (is (= [3 4] (remove-frame [10 3 4]))) (is (= [5] (remove-frame [6 4 5])))) (deftest test-remvo-two-frames (is (= [] (remove-frame (remove-frame [0 1 2 0 0 0]))))) (deftest test-scores (is (= 0 (score []))) (is (= 6 (score [1 2 3]))) (is (= 15 (score [1 2 3 4 5 0]))) (is (= 19 (score [10 1 2 3]))) (is (= 17 (score [5 5 1 2 3]))) (is (= 300 (score [10 10 10 10 10 10 10 10 10 10 10 10]))) (is (= 19 (score [5 5 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 ]))) (is (= 21 (score [10 1 1 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 ]))))Uncle Bob, we met several times at Xerox – you taught me the real intent of OOD/OOP. I’ve since moved on to C#, and some guys I work with decided to take a functional language course last fall to get mentally into modern days. The end result: you must think bottom-up for FP – get rid of the top-down mindset (for FP). Small-small functions (the mechanism code), no nested ifs, and your higher level functions (the policy code) just call the lower-level ones. Needing some closures is common. Map your tests to the functions after you are done. Once you are comfortable with the mindset, then you can start writing your tests first. Get comfortable with map/filter/reduce.