Sure, why not bowling in Elixir?
Why bowling?
Chet and I demonstrate pair programming and TDD in our Agile Developer Skills workshops, and elsewhere from time to time. The problem we generally solve is scoring a game of ten-pin bowling, and it goes like this:
Given the rolls for a legal game of bowling, calculate the final score for the game.
Note that in this specification we do not calculate the score frame by frame, only the final score. We do not handle any form of error, such as providing too few rolls, too many rolls, rolls dropping more than ten pins, or negative numbers of pins, and so on. The reason is that this problem, over-simplified though it is, provides us plenty of meat for a pair programming demo of an hour or ninety minutes, with full voice-over, witty repartee, and lots of TDD tests and refactoring.
It’s fun: you should have us in and we’ll do it for you.
But Elixir?
So, as you know, I’ve been playing with Elixir, just to bend my brain around a different way of programming. My focus has mostly been payroll, just because I know a lot of payroll stories by heart. But I was thinking about bowling, in the context of Elixir’s pattern matching.
As you look at the stream of rolls in ten-pin bowling, the rules turn out to be pretty simple:
- If the first roll you’re looking at is a 10, it’s a strike. Score 10 plus the next two rolls. Skip the 10 and move on.
- If the two rolls you’re looking add up to 10, it’s a spare. Score 10 plus the next one roll. Skip the two you’re looking at and move on.
- Else, score the sum of the two you’re looking at, skip them, and move on.
Written out this way, you’d wonder how we could make ninety minutes out of this but we develop the program in a certain style and we explain most every thought we have. But I digress …
Elixir has conditionals, but it’s more common to do things with pattern matching: you can define a function with several entry points, each one catering to a different pattern in the input. So I was thinking about that, in an Elixir kind of way:
- 10, bonus1, bonus2, whatever should score 10+bonus1+bonus2, and then go on to score bonus1, bonus2, whatever;
- roll1, roll2, bonus1, whatever, when roll1+roll2 equals 10, should score 10+bonus1, and then go on to score bonus1, whatever;
- roll1, roll2, whatever should score roll1+roll2, and then go on to score whatever.
It seemed like that should work. But this is a recursive solution, which is good in a list type language, but how will it stop, and how will it cumulate the score while it goes?
My first viable thought was to keep all the information on the list of rolls. I’d prefix it with the frame number and the cumulated score, and stop the recursion when the list starts with frame number ten, and return the score.
So scoring would go something like this:
- [0,0, 4,3, 6,4, 10, 5,4, etc]
- [1,7, 6,4, 10, 5,4, etc]
- [2,27, 10, 5,4, etc]
- [3,46, 5,4, etc]
- [4,55, etc]
In retrospect, it’s odd keeping weird information inside the list, but at the time it seemed the logical thing to do.1 We’ll deal with that below.
And some code …
Here’s the first working version:
defmodule Bowling do
def score([ 10, total | _tail ]), do: [score: total]
def score([frame, total, 10, b1, b2 | tail]) do
score([frame+1, total + 10 + b1 + b2, b1, b2 | tail ] )
end
def score([ frame, total, r1, r2, b1 | tail]) when r1 + r2 === 10 do
score([ frame+1, total + 10 + b1, b1 | tail])
end
def score([frame, total, r1, r2 | tail]), do: score([frame+1, total+r1+r2 | tail])
end
The code above scrolls sideways. There’s a story that goes with that. Read on.
The man still can’t TDD
I still don’t know how to TDD in Elixir (coming right up, I promise), so I typed in the initial guard clause and manually tested that. Then I typed in the last line, frame, amount, r1, r2
and manually tested ten rolls of 4,3 or something. That worked. Then I did strikes, then spares.
I’m not proud of doing it the old fashioned way but one does what one must with the meager tools in one’s head.
A notational note
One note about the notation like [10, total | _tail]
: I’m not sure we’ve spoken of this before. The [x, y | tail]
list pattern matches two elements, calling them x and y, and then the rest of the list, calling it tail. In our example here, we match a literal 10, call the second item total, and match on _tail
rather than just tail
. Putting in the underbar keeps the compiler from complaining when the variable isn’t used: it just means “ignored”.
The important part for our understanding is the use of |
to mark off the tail of the list. This can be used in a list constructor, as shown in the examples, when we construct the next list to score.
Moving right along
One discovery came late. I originally had this for the first guard:
def score([ 10, total ]), do: [score: total]
The difference is that the final version has [10, total | _tail
. When the final roll is a strike or spare, the game ends with the list containing 10, the final score, but with the bonus rolls still there, because they only count for bonus, not for themselves. Without allowing for the tail, the program crashes for lack of a match, trying to score frame 11.
I suspect I’d have made this mistake with or without TDD, and it would have shown up as soon as I did the mandatory perfect game test of a dozen tens.
So this was good: I had learned a bit about pattern matching in the definition of a function, and wound up with an interesting and very simple version of the bowling scoring program. I’m tempted to try it recursively the next time we do it in Java or C#.
An improved version
But keeping all the information on the list seems a bit off, because the list isn’t uniform in character: it begins with two special values and then is all about rolls. So I just hand-hammered the list version into one that keeps the special values in parameters of score, making it score(frame, total, rolls)
. All the changes were rote: just move the square bracket over, basically. The resulting version looks like this:
defmodule Bowling2 do
def score(10, total, rolls), do: [score: total]
def score(frame, total, [10, b1, b2 | tail]) do
score(frame+1, total + 10 + b1 + b2, [b1, b2 | tail ] )
end
def score( frame, total, [r1, r2, b1 | tail]) when r1 + r2 === 10 do
score( frame+1, total + 10 + b1,[ b1 | tail])
end
def score(frame, total, [r1, r2 | tail]), do: score(frame+1, total+r1+r2, tail)
end
OK, I think this is better, and it’s certainly compact as bowling scoring goes. Kind of a win for Elixir.
That said, Elixir’s notational oddities continue to bug me. Why is it ), do:
when the definition is going to be on the same line, but just ) do
when it’s going to be multiple lines? There are all these little things like this, the dot after function names that you need or don’t need, and so on. I’m learning them by rote, but they’re not making any sense to me. It’s better when one understands why, because it’s easier to write clear working code with understanding, and easier to spot mistakes as well.
Maybe I’ll figure it out or someone will explain it to me. If that happens, I’ll share it with you.
Things to improve
I’m tempted to make all the definitions use the do-end form. Having half of them in one syntax and half in another doesn’t look clean to me. In a given block of code, I think I’d use just one or the other, not both, certainly not switching back and forth as we have here.
I’ve been using very short names. That’s probably not good. Names like r1 and r2 make sense to me now, but will they make sense to the person who has to maintain this code at 3AM when the bowling alley goes down during the big 72 hour tournament? I’m doing this because a larger than usual fraction of my brain is busy figuring out list thinking, and Elixir syntax. And, of course, I’m not pairing. If I were, the names might be better. Anyway, that should be improved and with production code I’d say it should be fixed before calling the card done. This article is long enough, however, so I’ll not do it here. I promise to do it behind the scenes, though.
Oh, OK, I’ll do it now.
defmodule Bowling do
def score([ 10, total | _tail ]), do: [score: total]
def score([frame, total, 10, bonus_1, bonus_2 | tail]) do
score([frame+1, total + 10 + bonus_1 + bonus_2, bonus_1, bonus_2 | tail ] )
end
def score([ frame, total, roll_1, roll_2, bonus_1 | tail]) when roll_1 + roll_2 === 10 do
score([ frame+1, total + 10 + bonus_1, bonus_1 | tail])
end
def score([frame, total, roll_1, roll_2 | tail]) do
score([frame+1, total + roll_1 + roll_2 | tail])
end
end
defmodule Bowling2 do
def score(10, total, rolls), do: [score: total]
def score(frame, total, [10, bonus_1, bonus_2 | tail]) do
score(frame+1, total + 10 + bonus_1 + bonus_2, [bonus_1, bonus_2 | tail ] )
end
def score( frame, total, [roll_1, roll_2, bonus_1 | tail]) when roll_1 + roll_2 === 10 do
score( frame+1, total + 10 + bonus_1,[ bonus_1 | tail])
end
def score(frame, total, [roll_1, roll_2 | tail]) do
score(frame+1, total + roll_1 + roll_2, tail)
end
end
That’s better. Are you happy now? But seriously, thanks for keeping me honest. That’s what a pair is for.
The lines are long, though, and scroll sideways. They don’t even look great in my editor. I’m not sure how to improve that. Let’s try something. What if instead of
def score([frame, total, 10, bonus_1, bonus_2 | tail]) do
score([frame+1, total + 10 + bonus_1 + bonus_2, bonus_1, bonus_2 | tail ] )
end
we wrote
def score([frame, total, 10, bonus_1, bonus_2 | tail]) do
new_total = total + 10 + bonus_1 + bonus_2
score([frame+1, new_total, bonus_1, bonus_2 | tail ] )
end
This is the “explaining local variable” pattern, of course. Does it improve things? Honestly, I’m not sure. It does keep the code from scrolling sideways in the article, so that may make it worth while. OK, in for a penny, here we go:
defmodule Bowling do
def score([ 10, total | _tail ]), do: [score: total]
def score([frame, total, 10, bonus_1, bonus_2 | tail]) do
new_total = total + 10 + bonus_1 + bonus_2
score([frame+1, new_total, bonus_1, bonus_2 | tail ] )
end
def score([ frame, total, roll_1, roll_2, bonus_1 | tail])
when roll_1 + roll_2 === 10 do
new_total = total + 10 + bonus_1
score([ frame+1, new_total, bonus_1 | tail])
end
def score([frame, total, roll_1, roll_2 | tail]) do
score([frame+1, total + roll_1 + roll_2 | tail])
end
end
defmodule Bowling2 do
def score(10, total, rolls), do: [score: total]
def score(frame, total, [10, bonus_1, bonus_2 | tail]) do
new_total = total + 10 + bonus_1 + bonus_2
score(frame+1, new_total, [bonus_1, bonus_2 | tail ] )
end
def score( frame, total, [roll_1, roll_2, bonus_1 | tail])
when roll_1 + roll_2 === 10 do
new_total = total + 10 + bonus_1
score( frame+1, new_total,[ bonus_1 | tail])
end
def score(frame, total, [roll_1, roll_2 | tail]) do
score(frame+1, total + roll_1 + roll_2, tail)
end
end
OK, I do think this is better, and it only took a few moments to do. Less time than it took to write the corresponding paragraphs in fact. That’s usually the way of it with code improvement: if you just do it it takes almost no time at all.
Why are you doing all this?
If you’ve read this far, you may be wondering why I’m doing all this. If you’re not, you were almost certainly wondering it, but now you won’t know the answer, because you’re not reading this.
I’m certainly not doing it for the money. No one is paying me for this stuff, and few enough of you ever get in touch about a course. (I do have a PayPal account and a cash.me account, if you’re interested. Maybe someday I’ll put a support page on the site. But it is not this day.)
I’m doing it mostly to show that there’s at least one programmer in the world who doesn’t just automatically spit out code that’s fit for publication. I learn over time, I know languages imperfectly, I make stupid mistakes. Possibly, there is a reader out there who learns over time, knows things imperfectly, and makes stupid mistakes – and feels like the only one. I want that programmer to know that everyone is like that. Everyone fumbles around, tries things, makes mistakes, stumbles, gets up, stumbles again.
Sometimes the feeling that everyone around you knows all this stuff and you’re just faking it is called “Impostor Syndrome”. The thing to know is this: everyone is a fumbling idiot, doing their best to make it through. Either we’re all impostors, or none of us are.
At least that’s what I tell myself. Don’t disabuse me of the notion. Anyway, I make so many mistakes that I’m happy to share them. Plenty to go around.
Thanks!
===
-
Star Trek, Journey to Babel
Amanda: And you, Sarek, would you also say thank you to your son?
Sarek: I don’t understand.
Amanda: For saving your life.
Sarek: Spock acted in the only logical manner open to him. One does not thank logic, Amanda.
Amanda: Logic! Logic! I’m sick to death of logic! Do you want to know how I feel about your logic?
Spock: Emotional, isn’t she?
Sarek: She has always been that way.
Spock: Indeed. Why did you marry her?
Sarek: At the time it seemed the logical thing to do. ↩