99 Bottles of Beer (OCaml)

Other implementations: Alice ML | Erlang | Haskell | Inform 7 | Java | OCaml | Perl | Python | Ruby

A typical verse of the 99 bottles of beer song is:

99 bottles of beer on the wall, 99 bottles of beer.
Take one down and pass it around, 98 bottles of beer on the wall.

The verse is then repeated starting with the new count for the number of bottles left on the wall:

98 bottles of beer on the wall, 98 bottles of beer.
Take one down and pass it around, 97 bottles of beer on the wall.

The last three verses (two, one and zero bottles of beer on the wall) are special cases:

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 one down and pass it around, no more bottles of beer on the wall.

No more bottles of beer on the wall, no more bottles of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.

Note that when we get to one bottle of beer the verse changes from the plural bottles to the singular bottle, and the final line No more bottles of beer on the wall! of the song is different from all the preceding verses. The very last verse is also different from all the others.

The implementation

Because the song is so structured, with only the number of bottles changing in most verses, and pluralization and the next action to take changing in a few other verses, the song can easily be structured as a loop. Here, we use a recursive function `countdown`.

In `countdown`, we make use of two other auxiliary functions, `bottle_count` and `next_action`, which will compute for us the correct phrase to sing and the correct next action to take, given the number of bottles of beer we have on hand. If the number of beers we have on hand is zero or more, we compute these phrases, and assemble and print them via a printf statement before recursing with one less bottle. If, however, our beer count is negative, then we have reached our base case of the recursion and silently return.

```<<countdown>>=
let rec countdown n =
if n >= 0 then
let b = bottle_count n and b1 = bottle_count (n-1) and next = next_action n in
Printf.printf "%s on the wall, %s.\n%s, %s on the wall.\n\n" b b next b1;
countdown (n-1)```

So let us look at these helper functions. The first function, `bottle_count`, is fairly straight-forward, given the number of bottles of beer on hand, and returns the properly pluralized phase for the song. The only tricky points here are the last pattern match condition. In the first three conditions, we've covered all of the non-negative numbers of beer, which is all we would expect to encounter. However, adding a case to handle negative numbers allows us to easily process the final verse of the song.

Note that in each verse, the end of the second line tells us how many bottles we will have after taking one down. This works fine, so long as we have at least one bottle to take down, the first three cases of `bottle_count` handle that properly. But in the last verse, we no longer take down a bottle, as we have none, so instead, we head to the store to restock, back up to our original number of beers. If we add a case that handles negative beers, then we can compute the last half of the second line of each verse in the same way, by calling `bottle_count` with one less beer than we currently have. And so when we are out of beers, we will call it with an argument of -1, triggering this case. Now then, notice how this case works. As we have thrown out the value of the argument in this case, we do not use it in the construction of the phrase. Instead, we will use OCaml's closure properties to return a phrase using a value of n which comes from outside this function. As you will see, this value is the original number of beers we started with.

```<<bottle_count>>=
let bottle_count = function
| 0 -> "No more bottles of beer"
| 1 -> "1 bottle of beer"
| n -> (string_of_int n) ^ " bottles of beer"```

Next, we have `next_action`. This function is about as simple as it gets. If the number of beers we have on hand is zero, then we go to the store for more. Otherwise, if we still have some left, we take it down and pass it around.

```<<next_action>>=
let next_action = function
| 0 -> "Go to the store, buy some more"
| _ -> "Take one down, pass it around"```

Finally we put it all together with the wrapper function `bottles`. we pass in to `bottles` the number of bottles of beer we have to start with (sure, most folk start with 99, but we can start with 2 or 17, or 999999 if we so choose -- flexibility is a good thing). We then locally define our helper functions `bottle_count`, `next_action`, and `countdown` (note that this is where the final case of `bottle_count` gets its value for n), and then we do a quick sanity check on the number of beers we would like to start with. If that number is zero or less, then we raise an exception, and inform the caller that we are worried about the health of his liver. Otherwise, if we have some beer to tork with, we begin the countdown.

```<<beer99.ml>>=
let bottles n =
bottle_count
in
next_action
in
countdown
in
if n > 0 then countdown n
else failwith "Not even a single bottle of beer?  You must have a drinking problem..."
;;
```

Finally, we offer a sample invocation that will test our function with the traditional 99 beers.

```<<beer99.ml>>=
let () = bottles 99;;```