Scala Bowling Kata - somewhere in the middle... 9
- Installed the Eclipse Scala Plugin
- Installed Scala using Mac Ports
- Figured out how to get those things playing nice (the plugin page pretty much did that, but in a nutshell, add a few jar files to the classpath)
- We don’t need YADT – Yet another damn term
- Trait is a heavily overloaded word
- I like the term BDD better and it fits.
Anyway, one such choice was Specs, which is what I decided to use.
So back to yak shaving:- I added another jar to my classpath in Eclipse
- Then read how to get it running in Eclipse. Not too bad, I suppose.
So now I need to learn Scala. Sure, I’ve used it, but far less than Ruby. So it took me several hours to get specs running along with writing some Scala code to score a game – I’m glad I know the domain at least.
I wanted to make similar behaviors to the ones I wrote for the Ruby version, which I did.
However, unlike the Ruby version, I was curious what would happen if I:- Took an approach similar to Uncle Bob – strikes take one slot in an array
- Added input validation
On the one hand, there are some interesting things I managed to create. On the other hand, I’ve got a bit of a mess. I have a stateful object to avoid passing parameters so that I can write some of the code cleanly. I know I need to add in an intermediate computational object, and I’m going to get to that. However, I wanted to get feedback on what I’ve put out there so far.
Specifically,- What do you think of the (bdd-style) examples from specc?
- What is the correct way to write the Times(20).Do( ...) thing I came up with, there has be a better way?
- For the part of the bowling scoring code that is not stateful (read this as, does not violate the SRP), what do you think of it?
- How would you remove most/all of the state (other than the individual rolls) out of the Bowling scorer class? (Or would you choose to have the roll() method return a new instance of BowlingScorer with the new score recorded?)
- Notice that the class maintains a mini state machine in the form of tracking whether the first ball of he current frame (not tracked) has or has not been thrown. That’s only there to be able to perform input validation. I considered:
- Walking the array
- Going to 2 slots for every frame (making it easy to find the frame)
- Storing a frame object (ok, I didn’t really consider it, but I did think about it)
- The mini state machine
- nextFrameScore uses the index instance variable, and changes it. This both violates command-query separation and demonstrates a violation of the SRP, but it made the scoreAt method look nice.
An interesting side effect is that scoring marks (strikes and spares) uses the same approach, sum up three rolls total.
I know this needs work. What I’ve got works according to its current specification (its examples), so in a sense, that’s a good thing because I’ve already stared experimenting with trying out different solutions. However, I am painfully aware of how unaware I am of Scala at the moment, so your (hopefully gentle) feedback will tell me what I need to learn next.
Looking forward to the virtual beating …
Brett
Here are the two files I’ve created so far (and to be clear, all of the examples pass): BowlingScorerExampleGroup.scalapackage com.om.example
import org.specs._
object BowlingScorerExampleGroup extends SpecificationWithJUnit {
var scorer = new BowlingScorer();
def roll(value:Int) {
scorer.roll(value)
}
def rollMany(rolls:Int, value:Int) {
0.until(rolls).foreach { arg => scorer.roll(value) }
}
def haveAScoreOf(expected:Int) {
scorer.score must_== expected
}
def strike {
roll(10)
}
def spare {
rollMany(2, 5)
}
abstract class IDo {
def Do(block: => Unit)
}
def Times(count:Int): IDo = {
return new IDo {
def Do(block: => Unit) {
1.to(count).foreach( arg => block )
}
}
}
"A Newly Created Bowling Scorer" should {
haveAScoreOf(0)
}
"A game with all 0's" should {
Times(20).Do( roll(0) )
haveAScoreOf(0)
}
"A game with all 1's" should {
Times(20).Do { roll(1) }
haveAScoreOf(20)
}
"A game with a single spare followed by a 5" should {
spare
roll(5)
haveAScoreOf(20)
}
"A game with all 5's" should {
Times(10).Do( spare )
roll(5)
haveAScoreOf(150)
}
"A game with a single strike followed by a 4" should {
strike
roll(4)
haveAScoreOf(18)
}
"A game with a strike, spare then an open frame with two 3's" should {
strike
spare
Times(2).Do( roll(3) )
haveAScoreOf(39)
}
"A game with strike, spare then an open frame with two 3's" should {
spare
strike
Times(2).Do( roll(3) )
haveAScoreOf(42)
}
"A Dutch 200 game, Spare-Strike" should {
Times(5).Do {
spare
strike
}
spare
haveAScoreOf(200)
}
"A Dutch 200 game, Strike-Spare" should {
Times(5).Do {
strike
spare
}
strike
haveAScoreOf(200)
}
"A Perfect game" should {
Times(12).Do( strike )
haveAScoreOf(300)
}
"The score for each frame of a Perfect game, each frame" should {
Times(12).Do( strike )
1.to(10).foreach{ frame => scorer.scoreAt(frame) must_== 30 * frame }
}
"An individaul roll of > 10" should {
roll(11) must throwA[IllegalArgumentException]
}
"An iniviaul roll of < 0" should {
roll(-1) must throwA[IllegalArgumentException]
}
"A frame trying to contain more than 10 pins" should {
roll(8)
roll(3) must throwA[IllegalArgumentException]
}
}BowlingScorer.scala
package com.om.example
class BowlingScorer {
var rolls:Array[Int] = Array()
var index:Int = 0
var firstBallInFrameThrown: Boolean = false;
def roll(roll:Int) = {
validate(roll)
record(roll)
}
def validate(roll:Int) {
if((0).to(10).contains(roll) == false)
throw new IllegalArgumentException("Individaul rolls must be from 0 .. 10")
if(openScoreAt(indexToValidate) + roll > 10)
throw new IllegalArgumentException("Total of rolls for frame must not exceed 10");
}
def record(roll: Int) {
rolls = rolls ++ Array(roll)
firstBallInFrameThrown = firstBallInFrameThrown == false && roll != 10
}
def indexToValidate = {
if(firstBallInFrameThrown) rolls.length - 1 else rolls.length
}
def scoreAt(frame:Int) = {
1.to(frame).foldLeft(0) { (total, frame) => total + nextFrameScore }
}
def score = {
scoreAt(10)
}
def nextFrameScore = {
var result = 0;
if(isStrike(index)) {
result += markScoreAt(index)
index += 1
} else if(isSpare(index)) {
result += markScoreAt(index);
index += 2
} else {
result += openScoreAt(index);
index += 2
}
result
}
def isStrike(index:Int) = {
valueAt(index) == 10
}
def markScoreAt(index:Int) = {
sumNext(index, 3)
}
def isSpare(index:Int) = {
openScoreAt(index) == 10
}
def openScoreAt(index:Int) = {
sumNext(index, 2)
}
def sumNext(index:Int, count:Int) = {
index.until(index+count).foldLeft(0)(_ + valueAt(_))
}
def valueAt(index:Int) = {
if(rolls.length > index) rolls.apply(index) else 0
}
}
I have done the same Kata in Scala here: http://gist.github.com/188452
I feel my version looks less ‘java’ compared with this one.
BowlingScorer looks like a good fit for an immutable case class containing a List (adding a new roll to the list is constant time and folding over the list is quite fast), ie:
case class BowlingScorer(rolls : List[Int]) { /* Method implementations left as an excercise */ }
My first advice would be to avoid being so Lisp-ish… drop the parenthesis! And the dots, of course.
I feel that operator style notation reads more clearly.
As for “Times”... one could write a book on variations thereof.
For instance, as an implicit on a function:
implicit def toTimes(f: Function0[Unit]) = new AnyRef { def times(n: Int) = 1 to n foreach (_ => f()) }
Used like this:
(() => println(“Hello”)) times 5
Or as an implicit on Integer:
implicit def toTimes(n: Int) = new AnyRef { def times(f: => Unit) = 1 to n foreach (_ => f) } 5 times println(“Hello”)
As a curried function:
def times(n: Int)(f: => Unit) = 1 to n foreach (_ => f) times (5) (println(“Hello”))
Now, this is not faster implementation you could have. For that, “while” is unbeatable.
The thing is, “times”, as simply repeating something that returns nothing, is pretty useless… unless you are benchmarking, or using side effects! So, once you get rid of doing things in a side effectfull manner, what would be the choices?
Here is one possibility:
def rollMany(rolls: Int, value: Int, s: Score) = List.fill(rolls)(value).foldLeft(s) { (score, value) => score roll value } def spare(s: Score): Score = rollMany(2, 5, s)
Not very composable, though. Better to pass the function:
def rollMany(rolls: Int, f: Score => Score) = (s: Score) => (1 to rolls foldLeft s) { (score, _) => f(score) } val spare = rollMany(2, _ roll 5) val strike = (_: Score) roll 10
You can then compose:
val dutch = rollMany(5, strike andThen spare) andThen strike
You can then test it by calling it with an initial Score:
dutch(InitialScore)
At any rate, these are just a few ideas to prod you into direction you might not have considered.
An addition worth considering for a Concurrency for Java class is Jetlang (http://code.google.com/p/jetlang/). Jetlang is a message based concurrency Java library which virtually eliminates the need for locks.
One of the nicest features is that it makes testing very easy via the FiberStub, providing an example of how to nicely abstract away threading as suggested in the concurrency chapter of Clean Code.
The author, Mike Rettig, even partially integrated Jetlang into Scala (http://www.jroller.com/mrettig/).
I still don’t understand the attraction of BDD. When I see the output of BDD, it’s indistinguishable from what from my POV is just a good Test. In fact I’d call what I/we do much closer to hypothesis-testing, whether that’s about behavior (as it mostly is) or state (as it sometimes is).
I guess it’s a powerful enough shift in the minds of BDD’ers that they don’t mind pulling the rug out from under testing conversations. Am I supposed to say “test” or “spec”, “BDD” or “TDD”. Am I going to run my testsuite and see if I’m green? Oops wrong word.
Giulio Cesare Solaroli,
I had a look at your code on git hub. I like the better use of list expressions. Initially I did not like the verbosity of your tests, but upon reflection, it makes the rolls more clear, so I do like it.
However, you’ve done the easy part of the kata. Your code does no error checking, so until you’ve done that, your code will be incomplete. And it’s the error checking that makes the code uglier.
In fact, having gone through this, I’m going to generally change my BDD approach to introduce error checking sooner. In the past I was strictly mostly happy path and then edge cases. Now I’m reconsidering that approach.
Jesper Nordenberg,
Agreed. All of the state bothered me. However, I had a 3.5 hour flight so with all of my tests passing, I started slowly removing it. And I managed to get rid of all state. The bowling scorer is immutable. The roll method returns a new instance of a bowling scorer with an updated list of scores.
I’ll be posting it soon. I still have one (or 2?) ugly methods.
Daniel Sobral,
Thanks for all of the tips! I did some of what you recommended but I’ve got more to go. I’ll be posting an update with a few more questions.
Also used a foldLeft (I think of this as inject:into:) with a tuple to remove some of the state…
the pposr os really good