Scala Bowling Kata - somewhere in the middle... 44

Posted by Brett Schuchert Tue, 06 Oct 2009 04:33:00 GMT

I need to do some work with Scala to update our Concurrency in Java class. We want to demonstrate some other approaches to concurrency, e.g., Scala Actors (among others). I began by shaving yaks:
  • 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)
Next, I wanted to use some BDD tool. I’m going to try and stop using the term TDD simply because the T, which stands for Test in TDD, really means “desired behavior”. I considered calling it Trait Driven Development, but:
  • 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.scala
package 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
  }
}

Comments

Leave a response

  1. Avatar
    Giulio Cesare Solaroli about 2 hours later:

    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.

  2. Avatar
    Jesper Nordenberg about 3 hours later:

    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 */ }

  3. Avatar
    Daniel Sobral about 8 hours later:

    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.

  4. Avatar
    Graham about 9 hours later:

    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/).

  5. Avatar
    Steve C about 10 hours later:

    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.

  6. Avatar
    Brett L. Schuchert 1 day later:

    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.

  7. Avatar
    Brett L. Schuchert 1 day later:

    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.

  8. Avatar
    Brett L. Schuchert 1 day later:

    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…

  9. Avatar
    FLV extractor 6 months later:

    the pposr os really good

  10. Avatar
    Pandora about 1 year later:

    These databases are typically managed with functional techniques, like map-reduce.

  11. Avatar
    www.whiteiphone4transformer.com about 1 year later:

    The christmas time is comeing, white iphone 4 conversion kit will be the best present for yourself and family.

  12. Avatar
    Silicone Molding about 1 year later:

    Mold making is the core business of Intertech (Taiwan). With world level technology, Intertech enjoys a very good reputation for making Injection Mold and Plastic Moldsfor their worldwide customers.

  13. Avatar
    Free Articles about 1 year later:

    The benefits of print on demand
    Watercan, Leaders Of Clean Water Projects In Developing Countries

  14. Avatar
    iPhone SMS to Mac Backup about 1 year later:

    Thanks for shareing! I agree with you. The artical improve me so much! I will come here frequently. Would you like to banckup iphone SMS to mac, macBook, macbookPro as .txt files? Now a software iphone SMS to Mac Backup can help you to realize it.

  15. Avatar
    iPad to Mac Transfer about 1 year later:

    I really like this essay. Thank you for writing it so seriously. I want to recommend it for my friends strongly. iPad to Mac Transfer can help you transfer music, movie, photo, ePub, PDF, Audiobook, Podcast and TV Show from ipad to mac freely.

  16. Avatar
    evaporator about 1 year later:

    Your site is amazing.I am very impressed to see this,i want to come back for visiting your site.Keep doing Good as well as you can.

  17. Avatar
    mobile screening equipment about 1 year later:

    KEESTRACK?manufacturing expert of mobile crusher and mobile screening equipment. Company engaged in the research & development, manufacture, sales of track screening machine,mobile screening,cone crusher,jaw crusher,impact crusher,mobile screening equipment,mobile crushing equipment, till now we are proud to say we are at front of the industry.

  18. Avatar
    ssara about 1 year later:

    The new installation is very must complicated It should be more clear. daycare grants

  19. Avatar
    zby love about 1 year later:

    In place of a wheel train to add up the beats into seconds, minutes, and hours, it used kids sports watchdigital counters. The higher Q factor of the resonator, along with quartz’s low temperature coefficient, resulted in better accuracy than the best mechanical watches, while the elimination of all moving parts made the watch more shock-resistant and eliminated the need sport chronograph watchesfor periodic cleaning.

  20. Avatar
    daycare job about 1 year later:

    Amazing! This is very informative blog. Thanks for sharing such a nice information with us. I appreciate your blogdaycare job

  21. Avatar
    Foreclosure Help in Alaska about 1 year later:

    I got these guides very helpful. Here I got much stuff to know..Foreclosure Help in Alaska

  22. Avatar
    Foreclosure Help in Alaska about 1 year later:

    Thank You for this wonderful post!Ohio Daycare Grants

  23. Avatar
    okey oyunu oyna about 1 year later:

    yes it is very nice.

    internette görüntülü olarak okey oyunu oyna, gerçek kisilerle tanis, turnuva heyecanini yasa.

  24. Avatar
    New Hampshire Daycare Form about 1 year later:

    Nice posts! thumbs up!New Hampshire Daycare Form

  25. Avatar
    coach purses about 1 year later:

    Mr Coates coach purses is the longest U.S. market popular with one of the most successful leather brand. Mr Coates coach purses store represents the most admirable American fashion innovative style and traditional skills . Mr Coates coach bags have durable quality and exquisite technology, Conspicuous Coach Heels in the female consumers have good reputation. Welcome to our shop Elegant Coach Purses

  26. Avatar
    New Hampshire Daycare Form about 1 year later:

    Good effort! keep up the good work! wypall x60

  27. Avatar
    absorbant about 1 year later:

    I got these guides very helpful. Here I got much stuff to know..absorbant

  28. Avatar
    Illinois Foreclosure Help about 1 year later:

    I got these guides very helpful. Here I got much stuff to know.. Illinois Foreclosure Help

  29. Avatar
    Daycare Grants in South Dakota about 1 year later:

    I got these guides very helpful. Here I got much stuff to know..Daycare Grants in South Dakota

  30. Avatar
    beats by dre store about 1 year later:

    Would you like to banckup iphone SMS to mac, macBook, macbookPro as .txt files? Now a software iphone SMS tohigh quality headphones new design headphones

  31. Avatar
    bagsupplyer about 1 year later:

    It is nice of you to post this.I will pay more attention on it. Discount fashion women Prada Lowcut shoes from China for wholesale free shipping,more order,more discount

  32. Avatar
    what is an iva about 1 year later:

    To read a worth knowing article is so overwhelming.

  33. Avatar
    Louboutins over 2 years later:

    asdasd +9f8as9+d9fs

  34. Avatar
    moncler over 2 years later:

    asdfs fgh98f7g88f8f8f

  35. Avatar
    Christian over 2 years later:

    asa sdf+09a8s0+ 90s8a9sss

  36. Avatar
    Tips For Bowling over 2 years later:

    Berkeley had a liberal element in the student body who tended to be quite active. I think that’s in general a feature of intellectually active places.

  37. Avatar
    christian louboutin over 2 years later:

    Berkeley had a liberal element in the student body who tended to be quite active. I think that’s in general a feature of intellectually active places.

  38. Avatar
    Supra Cuttler over 2 years later:

    As some conservative Republicans sought an alternative to Mitt Romney Supra Pilot Shoes

  39. Avatar
    garage wall storage over 2 years later:

    You still have an extremely beneficial weblog I have already been here looking at for about quite some time currently. I am a newcomer plus your achievement is extremely significantly a great inspiration personally. Carry on the nice article!

  40. Avatar
    iPhone contacts backup over 2 years later:

    It is so hard to understand this. can u explain more about this topic.

  41. Avatar
    homeopathic adhd remedies over 2 years later:

    Many thanks regarding sharing us all about this revise. We imagine you will not find fatigued about generating posts as useful simply because this.

  42. Avatar
    bladeless fans over 3 years later:

    Scala Bowling Kata – somewhere in the middle… 41 good post36

  43. Avatar
    louboutin sales over 3 years later:

    Scala Bowling Kata – somewhere in the middle… 42 hoo,good article!!I like the post!177

  44. Avatar
    www.Car-BatteryPrices.com over 3 years later:

    Which is a number of motivational things. Never knew that ideas could be this particular different.

Comments