Created
April 21, 2021 21:37
-
-
Save holli-holzer/9904da5be6cee3776cc814f3186cdfdd to your computer and use it in GitHub Desktop.
99 bottles of beer in Raku
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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