99 Bottles of Beer (Alice ML)

From LiteratePrograms

Jump to: navigation, search
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, 99 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

By using functions we can narrow down the 100 verses and contend with the differences. To begin, we'll declare a kind of exception that can be thrown when an invalid parameter is supplied. The bottles function will expect a value to be between 0 and 99. Anything out of that range will raise an exception which has a string attached that should describe the nature of the problem.

<<bottles.aml>>=
exception Bottles of string

Before we begin with the singing, we can use a helper function to sort out the singular or plural form of how to say "n bottles of beer".

<<bottles.aml>>=
fun bottle_count 0 = "no more bottles of beer"
  | bottle_count 1 = "1 bottle of beer"
  | bottle_count n = (Int.toString n ^ " bottles of beer")

With that out of the way, we can construct the main function. The function will be split into two special cases. The first is the verse when the tune is seeded with the value 0. This is the base case in that it is the very last verse that is sung. The second part of the function will be used to sing every verse except the last one. And just in case the function is called outside of the expected domain of 0 to 99, we raise an exception to indicate an invalid input.

<<bottles.aml>>=
fun bottles 0 =
      let
         val finish = bottle_count 99
      in
         print ("No more bottles of beer on the wall, no more bottles of beer.\n");
         print ("Go to the store and buy some more, " ^ finish ^ " on the wall.\n\n")
      end
  | bottles n if (n >= 1 andalso n <= 99) =
      let
         val start = bottle_count n
         val finish = bottle_count (n-1)
      in
         print (start ^ " on the wall, " ^ start ^ ".\n");
         print ("Take one down and pass it around, " ^ finish ^ " on the wall.\n\n");
         bottles (n-1)
      end
  | bottles _ = raise Bottles "You're too drunk to count!\n"

Now that we have a function built, we can invoke that function whenever we want to sing '99 Bottles. The function is used to create a side-effect (printing of verses) and returns nothing useful to the caller (though technically the function returns a value of type unit). Because we only use the function for side-effects and are not interested in the return value, we can assign the results to the wildcard val _ =, which effectively ignores any values returned from the function call.

<<bottles.aml>>=
val _ = bottles 99
Views