Skip to content

Instantly share code, notes, and snippets.

@holli-holzer
Created April 21, 2021 21:37
Show Gist options
  • Save holli-holzer/9904da5be6cee3776cc814f3186cdfdd to your computer and use it in GitHub Desktop.
Save holli-holzer/9904da5be6cee3776cc814f3186cdfdd to your computer and use it in GitHub Desktop.
99 bottles of beer in Raku
# Raku feature: Lazy list. This construct will repeat 99 to 0
# and then start over at 99 ad infintum when iterated
my @bottles = flat (99...0) xx Inf;
# for every index, generate the "xx bottles of beer"
# when using double quotes the Raku parser interprets
# code in curlies as, well, code and embeds the return
# value within the string
# in the first block we see an ordinary logical or
# in the second we see a postfix form of 'if'
my @bottles-of-beer = @bottles.map: -> $n {
"{$n || 'no more'} bottle{ 's' if $n != 1 } of beer"
};
# generate the first line of each verse
# Raku also has simple string interpolation
my @beers-on-the-wall = @bottles-of-beer.map: -> $bottles-of-beer {
"$bottles-of-beer on the wall, $bottles-of-beer\n".tc
};
# Here we see the Z operator at work. It zips together two (or more) lists and returns one value from each.
# generate the second line of each verse
# Note the deconstruction of the incoming sublist into two variables
my @beers-to-get = ( @bottles Z @bottles-of-beer.skip ).map: -> ( $beers-on-the-wall, $beers-available ) {
"{ $beers-on-the-wall ?? 'Take one down and pass it around' !! 'Go to the store and buy some more' }, $beers-available on the wall.\n"
};
# This is the first time any of the code above actually runs because everything is lazy
# Only when we really ask for values the code runs
# Here we ask for the first 100 values which is one time the whole song
# The ^100 is just a short form to write the range 0..99
.say for ( @beers-on-the-wall Z @beers-to-get )[ ^100 ]>>.join;
# Now, here's the kicker. Since the original @indices sequence is infinite we can easily
# display the song twice
#.say for @lyrics[ ^200 ];
# Or even forever
#.say for @lyrics;
# A more classical approach using "normal" subroutines
# and branchless logic
sub bottles-of-beer ( Int $number-of-bottles )
{
# state variables get initialized only once
state @plurals = '', 's';
state @bottles = 'no more', 1...99;
# simple string quoting and interpolation at work
return "@bottles[ $number-of-bottles ] bottle@plurals[ $number-of-bottles != 1 ] of beer";
};
sub bottles-of-beer-song
{
state @bottles = 99...0, 99;
state @actions = 'Go to the store and buy some more',
'Take one down and pass it around';
# the "rotor" function provides a sliding window across a list
# "gather/take" is a mechanism to a construct (lazy) sequence, the values
# coming from calls to "take" in the (dynamic) scope of the "gather"
gather for @bottles.rotor( 2 => -1 ) -> ( $on-the-wall, $available )
{
my $beers-on-the-wall = bottles-of-beer( $on-the-wall );
my $beers-available = bottles-of-beer( $available );
# multiline string quoting with an example of
# complex interpolation; everything in curly quotes is Raku code
# that gets executed in the context of the string
# "tc" is a Unicode aware form of Perls "ucfirst"
# it will capitalize the string if possible and is otherwise a No-Op
# all "Ints" are "True" except for zero.
# "so" returns the truthness of its argument
# the array lookup [] enforces Integer context on the index
# The integer values for Booleans are 0 and 1
# So "@array[ so $n ]" returns the first element of @array if $n is zero,
# otherwise it returns the second element
take qq:to/END/
{ $beers-on-the-wall.tc } on the wall, $beers-on-the-wall.
@actions[ so $on-the-wall ], $beers-available on the wall.
END
}
}
.say for bottles-of-beer-song;
# An object oriented approach
# We inherit from an "Int", an internal type
# That's not something you usually do, but we're showing off here, aren't we?
class Beers is Int
{
# Since this is Raku and Raku is written in Raku (mostly)
# we can easily override how our class stringifies
# in contrast to a regular Int
method Str
{
state @s = '', 's';
"{ +callsame() || 'no more' } bottle{ @s[ self != 1 ] } of beer"
}
method next
{
state @actions =
'Go to the store and buy some more',
'Take one down and pass it around';
@actions[ self > 0 ]
}
}
# We override the builtin -- operator for our class
multi sub postfix:<-->( Beers $n is rw )
{
$n = Beers.new: $n - 1;
}
class Beers-On-The-Wall-Song
{
# read only instance variable
# can only be set at construction time
# Raku is automatically creating a standard constructor for us
has Int $.number-of-beers = 99;
# when a multi method is called the raku dispatcher
# choses the one with the signature that best matches the arguments
# the power of this is not really shown here
multi method sing( Int $times )
{
self.sing for ^$times;
}
multi method sing
{
my $beers = my $all-beers = Beers.new: $.number-of-beers;
# Yes, Raku also has the usual control structures if you need them
until $beers == 0
{
say "{ $beers.tc } on the wall, $beers";
say "{ $beers.next }, { $beers-- } on the wall.\n"
}
say "{ $beers.tc } on the wall, $beers";
say "{ $beers.next }, $all-beers on the wall\n";
}
}
Beers-On-The-Wall-Song
.new( number-of-beers => 4 )
.sing(2);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment