Thursday, July 7, 2011

99 Bottles

The 99 Bottles Of Beer project has an entry for Factor. Unfortunately, it's for version 0.83 (latest released version is 0.94) and some minor changes have come into the language since then. Below, I contribute an updated version:

USING: formatting io kernel math math.ranges sequences ;

: verse ( n -- )
    dup "%d bottles of beer on the wall, " printf
    dup "%d bottles of beer.\n" printf
    "Take one down and pass it around, " write
    1 - "%d bottles of beer on the wall.\n" printf ;

: verse-1 ( -- )
    "1 bottle of beer on the wall, " write
    "1 bottle of beer." print
    "Take one down and pass it around, " write
    "no more bottles of beer on the wall." print ;

: verse-0 ( -- )
    "No more bottles of beer on the wall, " write
    "no more bottles of beer." print
    "Go to the store and buy some more, " write
    "99 bottles of beer on the wall." print ;

: 99bottles ( -- )
    99 2 [a,b] [ verse ] each verse-1 verse-0 ;

Shorter?

We can shorten this a bit by taking a few poetic liberties with the song. While doing that, we can make it flexible to allow any (positive) number of bottles:

USING: formatting io kernel math math.ranges sequences ;

: verse ( n -- )
    dup "%d bottles of beer on the wall, " printf
    dup "%d bottles of beer.\n" printf
    "Take one down and pass it around, " write
    1 - "%d bottles of beer on the wall.\n" printf ;

: last-verse ( -- )
    "Go to the store and buy some more, " write
    "no more bottles of beer on the wall!" print ;

: bottles ( n -- )
    1 [a,b] [ verse ] each last-verse ;

This solution is similar to the one posted on the Rosetta Code project.

Longer!

You might notice that a lot of the text is duplicative, so perhaps we can improve this solution by factoring out parts of the text into small and reusable functions.

USING: combinators formatting io kernel math sequences ;

: #bottles ( n -- str )
    {
        { 1 [ "1 bottle" ] }
        { 0 [ "no more bottles" ] }
        [ "%d bottles" sprintf ]
    } case " of beer" append ;

: on-the-wall ( n -- )
    #bottles dup "%s on the wall, %s.\n" printf ;

: take-one-down ( n -- )
    "Take one down and pass it around, " write
    #bottles "%s on the wall.\n" printf ;

: take-bottles ( n -- )
    [ dup zero? ] [
        [ on-the-wall ] [ 1 - dup take-one-down ] bi
    ] until on-the-wall ;

: go-to-store ( n -- )
    "Go to the store and buy some more, " write
    #bottles "%s on the wall.\n" printf ;

: bottles ( n -- )
    [ take-bottles ] [ go-to-store ] bi ;

It's a bit longer than the original and, in several ways, not as easy to understand. If we wanted to reuse this functionality elsewhere, it's a clear win. But, if we want to simply generate the "bottles song", perhaps the first or second way is better.

Extra Credit

For fun, I thought we could use our computer to sing to us (if you're using Mac OS X):

( scratchpad ) [ 99bottles ] with-string-writer
               "say \"%s\"" sprintf try-process

The code for this is on my Github.

No comments: