As a member of the Chrysler C3 team, I was challenged to learn how to build systems using Object Oriented techniques. One of the tools I and others on the team used was the comp.lang.smalltalk discussion group. Late in 1995, someone posted a message to most of the comp.lang groups asking the a program be posted in the group's language showing how the lyrics to the song '99 Bottles of Beer on the Wall' should be generated. There were several postings in the Smalltalk group, most of which were not too inspiring. I then saw the code listed below. A little while later, we decided we needed help performance tuning our application, and I suggested we talk to this Beck guy.
It turns out that our code was too much like that that did not impress me and with Kent's (,and Ron Jeffries's, Martin Fowler's, Jim Huangs's, and Ken Auer's) we built a system that looked much more like this one.
Subject: Re: 99 bottles of beer, auf Smalltalk
Date: 1996/01/26 Author: Kent Beck
There has been a discussion recently on comp.lang.smalltalk about how best to represent the song "99 Bottles of Beer on the Wall" in Smalltalk. Apparently, there has been some use of this song as a way of benchmarking various programming languages.
I found the Smalltalk implementations appalling in their lack of use of the facilities that make Smalltalk so powerful. In particular, the previously published solutions all made heavy use of iteration and conditional logic.
In my quest for intellectual purity, the limits of absurdity, and a decent distraction from this nasty cold, I have undertaken to write the one true definitive Smalltalk version. I hereby place the following code, written in literate program form, into the public domain, to stand as a monument for the ages, or until my hard disk crashes again, whichever comes first.
We need a program which takes as input, N, a number of bottles of beer and produces as output a string containing the N+l verses of the bottles of beer song. For N=3, the expected output is:
'3 bottles of beer on the wall 3 bottles of beer Take one down and pass it around 2 bottles of beer on the wall
2 bottles of beer on the wall 2 bottles of beer Take one down and pass it around 1 bottle of beer on the wall
1 bottle of beer on the wall 1 bottle of beer Take it down and pass it around No bottles of beer on the wall
No bottles of beer on the wall No bottles of beer There are no more to pass around No bottles of beer on the wall '
Note the subtle changes, verse to verse, that will necessitate the application of powerful object-oriented techniques. The third line changes, as does the plurality of the number of bottles.
The fundamental question in any object oriented development is "what is my first object?" Until this question is answered aright, nothing else the developer does will be of much use. Fortunately, we have in "song" a natural unit of computation, the Verse. Introducing varying kinds of Verses will allow us to capture with polymorphic messages logic that must be expressed as explicit conditionals in procedural languages. Such future flexibility is of uncertain use with a problem like BOB (Bottles of Beer song), but that's the thing about future requirements, you don't know what they're going to be.
Object subclass: #Verse instanceVariableNames: 'stream' classVariableNames: '' poolDictionaries: ''
Each Verse will sing itself onto a Stream by singing its verse, then asking the next verse to sing itself.
Verse>>sing self singVerse. stream cr. self nextVerse sing
The recursion ends when you get to a Verse which represents a negative number of bottles of beer, the NegativeVerse. Many developers would have subclassed Verse to define NegativeVerse, but since it need only implement a single message, sing, and since there is no code sharing involved, I chose to subclass directly from object. This leaves me the option to factor Verse inheritance some other way, should that prove important in the future.
Object subclass: #NegativeVerse instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' NegativeVerse >>sing "Do nothing"
We will use other variations on Verse to capture the special cases in the song. PrecedingVerse will sing the Nth to second verse. PenultimateVerse will sing the verse in which there is one lonely bottle left, and UltimateVerse will sing that sad sad verse in which all the bottle are gone and their contents drained.
The Verse instance creation method is a Factory Method, returning a Verse of the correct subclass depending on the bottle count.
Verse class>>Count: aninteger stream: aStream aninteger < 0 ifTrue: ^NegativeVerse new aninteger = 0 ifTrue: ^UltimateVerse stream: aStream. aninteger = 1 ifTrue: ^PenultimateVerse stream: aStream. ^(PrecedingVerse stream: aStream) setCount: aninteger
Singing a Verse
The singing of a Verse is represented by a method, each line of which causes the singing of one line of the song. In the future, it may be wise to further objectify the solution by creating a family of Line objects which sing individual lines . At this time, however, the solution does not seem to require this flexibility.
Verse> >singVerse self bottlesOfBeerOnTheWall; bottlesOfBeer; takeOneDown; nextVerseBottlesOfBeerOnTheWall
The First Line
The first line is sung by singing the number of bottles, followed by "of bottles of beer on the wall".
Verse>>bottlesOfBeerOnTheWall self countBottles. stream nextPutAll: ' of beer on the wall'; cr
The count of bottles of beer is sung by singing the number of bottles, followed by a space and the word "bottle", appropriately pluralized:
Verse>>countBottles stream nextPutAll: self bottleCountString; space; nextPutAll: self bottlesString
The default implementation of bottleCountString prints the number of bottles in the receiver:
Verse>>bottleCountString ^self count printString
On the last verse, however, the word "No" is printed, rather than a number. This is represented by overriding bottleCountString in UltimateVerse:
The pluralization of the word "bottle" is handled by implementing the default plural version in Verse, and overriding in PenultimateVerse:
Verse>>bottlesString ^'bottles' PenultimateVerse>>bottlesString ^'bottle'
Counting and the Various Verses The count of bottles is explicitly represented by an instance variable in PrecedingVerse:
Verse subclass: #PrecedingVerse instanceVariableNames: 'count' classVariableNames: poolDictionaries: '' PrecedingVerse>>setCount: aninteger count := aninteger PrecedingVerse>>count ^count
The other verses, subclasses of Verse with no additional instance variables, represent their counts implicitly in the accessing method for "count":
The second line is computed in much the same way as the first.
Verse>>bottlesOfBeer self countBottles. stream nextPutAll: ' of beer'; cr
The third line shows variation in both the last and second to last verses. All the other verses sing it with:
PrecedingVerse>>takeOneDown stream nextPutAll: 'Take one down and pass it around'; cr PenultimateVerse>>takeOneDown stream nextPutAll: 'Take it down and pass it around'; cr UltimateVerse>>takeOneDown stream nextPutAll: 'There are no more to pass around'; cr
The final line is a bit strange. The last line of one verse is the same as the first line of the next, so the obvious implementation is to send "self nextVerse bottlesOfBeerOnTheWall". However, the result of sending nextVerse to an UltimateVerse is a NegativeVerse, which doesn't know how to bottlesOfBeerOnTheWall. We would either have to duplicate the code from UltimateVerse in NegativeVerse or somehow send a message back to the UltimateVerse. Instead, we explicitly encode the fact that the final two verses have the same last line by hiding it behind a concatenated message, nextVerseBottlesOfBeerOnTheWall. The default implementation is simple:
Verse>>nextVerseBottlesOfBeerOnTheWall self nextVerse bottlesOfBeerOnTheWall UltimateVerse overrides this to send itself the message bottlesOfBeerOnTheWall:
UltimateVerse>>nextVerseBottlesOfBeerOnTheWall self bottlesOfBeerOnTheWall
Instances of the subclasses of Verse are created by passing along the Stream to be sung on. This is not public protocol, however, and is intended to be used only by the Verse class>>count:on: method.
Verse class>>Stream: aStream ^self new setStream: aStream Verse>>SetStream: aStream stream:= aStream
The next verse is computed by decrementing the count by one and sending it to the FactoryMethod in Verse:
Verse>>nextVerse ^verse count: self count - 1 stream: stream
Finally, Verse provides a convenient Facade to sing any number of verses:
Verse class>>sing: aninteger |writer| writer := WriteStream on: String new. (self count: aninteger stream: writer) sing. ^writer contents
This program's use of a Stream for concatenation makes it generally efficient. Here is a performance profile run with Profile/V (you were just waiting for the commercial plug, weren't you) and gathered on the recursive call to Verse>>sing.
100% (177) Verse>>sing ...
- 88% (155) Verse>>SingVerse ...
- :31% (54) Verse>>nextVerseBottlesOfBeerOnTheWall ...
- : :25% (44) Verse>>bottlesOfBeerOnTheWall ...
- : : :20% (35) Verse>>CountBottles ...
- : : : :14% (25) Verse>>bottleCountString ...
- : :5% (8) Verse>>nextVerse
- :26% (46) Verse>>bottlesOfBeerOnTheWall ...
- : :20% (35) Verse>>countBottles ...
- : : :15% (26) Verse>>bottleCountString ...
- :22% (39) Verse>>bottlesOfBeer .. .
- : :18% (31) Verse>>countBottles .. .
- : : :10% (18) Verse>>bottleCountString ...
- :8% (15) Verse>>takeOneDown .. .
- 10% (17) Verse>>nextVerse .. .
NextVerse is redundantly computed twice, so its total of 15% could be cut in half. BottleCountString is computed three times, so its total could be cut from 39% to 13%. This could be further reduced by printing the bottle count directly on the stream, without creating an intermediate String.
With a bit of work, this program could be expanded to encompass a framework for singing repetitive songs of all kinds. At present, however, it stands as a monument to that sense of the ridiculous that is all that gets us through some times. Long may it wave.