Going down chimneys is a dangerous business.
Chimneys can be narrow, high, and sometimes not well constructed to begin with.
This year, Santa wants to be prepared. Therefore, he is combining a chimney inspection with the delivery of presents.
A chimney inspection involves ensuring that every layer of bricks is at the correct height; i.e. that the layers of mortar are consistent, and that the bricks are also a consistent height.
For instance, for bricks that are 2¼” high, and mortar that is ⅜” thick, the sequence of measurements should look like this:
🎅 ─██─ || layer total ░░░░░░░░░░ ░░░░░░░░░░░░░░░ ░░░░░░░░░░ 2¼ ░░░░░░░░░░ ░░░░░░░░░░░░░░░ ░░░░░░░░░░ ░░░░░░░░░░ ░░░░░░░░░░░░░░░ ░░░░░░░░░░ ⅜ ‾‾??? ░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░ 2¼ ░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░ ░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░ ⅜ ‾‾5⅝ ░░░░░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ 2¼ ░░░░░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ⅜ ‾‾3 ░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░ 2¼ ░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░ ░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░ ⅜ _____________________________________‾‾⅜
The plan is for the Elves to do the dangerous descent to the bottom, tape measure in hand, and then come back up, ensuring that the top of each brick layer is at precisely the correct place on the tape measure.
One particular Elf, named Elvis, has taken it upon himself to write a program to help out with the task of computing this sequence of heights.
Being lazy, Elvis did not even want to add any of the fractions above, and wanted the program to do all the work. He also did not want to exert the mental effort required to figure out the formula for the height of each layer. Luckily, he was using Perl 6, which properly turns unicode fractions into rational numbers (type Rat
), and also has a sequence operator (...
) which figures out arithmetic sequences based on the first few terms.
So, Elvis’ first cut at the program looked like this:
my @heights = 0, ⅜, 3, 5+⅝ ... *; say @heights[^10].join(', ')
This gave him the first 10 heights that he needed:
0, 0.375, 3, 5.625, 8.25, 10.875, 13.5, 16.125, 18.75, 21.375
While this was correct, it was hard to use. The tape measure had fractions of an inch, not decimals. The output Elvis really wanted was fractions.
Fortunately, he knew that using join
turned the Rat
s into strings, Turning a Rat
into a Str
is done by calling the Str
method of the Rat
class. So, by modifying the behavior of Rat.Str
, he figured he could make the output prettier.
The way he decided to do this was to wrap
the Str
method (aka using the decorator pattern), like so:
Rat.^find_method('Str').wrap:
sub ($r) {
my $whole = $r.Int || "";
my $frac = $r - $whole;
return "$whole" unless $frac > 0;
return "$whole" ~ <⅛ ¼ ⅜ ½ ⅝ ¾ ⅞>[$frac * 8 - 1];
}
In other words, when stringifying a Rat, return the whole portion unless there is a fractional portion. Then treat the fractional portion as the number of eighths, and use that as an index into an array to look up the right unicode fraction.
He combined that with his first program to get this sequence of heights:
0, ⅜, 3, 5⅝, 8¼, 10⅞, 13½, 16⅛, 18¾, 21⅜
“Hooray!” he thought. “Exactly what I need.”
Santa took a look at the program and said “Elvis, this is clever, but not quite enough. While most brick dimensions are multiples of ⅛ , that might not be true of mortar levels. Can you make your program handle those cases, too?”
“Sure” said Elvis with a wry smile. And he added this line into his wrapper function:
return "$whole {$frac.numerator}⁄{$frac.denominator}"
unless $frac %% ⅛;
using the “is divisible by” operator (%%
), to ensure that the fraction was evenly divisible into eighths, and if not to just print the numerator and denominator explicitly. Then for mortar that was ⅕” thick, the sequence:
my @heights = 0, ⅕, ⅕ + 2+¼ + ⅕, ⅕ + 2+¼ + ⅕ + 2+¼ + ⅕ ... *; say @heights[^10].join(', ');
0, 1⁄5, 2 13⁄20, 5 1⁄10, 7 11⁄20, 10, 12 9⁄20, 14 9⁄10, 17 7⁄20, 19 4⁄5
“Actually”, Santa said, “now that I look at it, maybe this isn’t useful — the tape measure only has sixteenths of an inch, so it would be better to round to the nearest sixteenth of an inch.”
Elvis added a call to round
to end up with:
Rat.^find_method('Str').wrap:
sub ($r) {
my $whole = $r.Int || '';
my $frac = $r - $whole;
return "$whole" unless $frac > 0;
my $rounded = ($frac * 16).round/16;
return "$whole" ~ <⅛ ¼ ⅜ ½ ⅝ ¾ ⅞>[$frac * 8 - 1] if $rounded %% ⅛;
return "$whole {$rounded.numerator}⁄{$rounded.denominator}";
}
which gave him
0, 3⁄16, 2⅝, 5⅛, 7 9⁄16, 10, 12 7⁄16, 14⅞, 17¼, 19 13⁄16
He showed his program to Elivra the Elf who said, “What a coincidence, I wrote a program that is almost exactly the same! Except, I also wanted to know where the bottoms of the layers of bricks are. I couldn’t use a sequence operator for this, since it isn’t an arithmetic progression, but I could use a lazy gather and an anonymous stateful variable! Like this:
my \brick = 2 + ¼;
my \mortar = ⅜;
my @heights = lazy gather {
take 0;
loop { take $ += $_ for mortar, brick }
}
Elvira’s program produced:
0, ⅜, 2⅝, 3, 5¼, 5⅝, 7⅞, 8¼, 10½, 10⅞
i.e. both the tops and the bottoms of the layers of bricks:
\ 🎅 / ██ || layer total ░░░░░░░░░░ ░░░░░░░░░░░░░░░ ░░░░░░░░░░ 2¼ ░░░░░░░░░░ ░░░░░░░░░░░░░░░ ░░░░░░░░░░ ░░░░░░░░░░ ░░░░░░░░░░░░░░░ ░░░░░░░░░░ ⅜ ‾‾8¼ ░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░‾‾7⅞ 2¼ ░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░ ░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░ ⅜ ‾‾5⅝ ░░░░░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░‾‾5¼ 2¼ ░░░░░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░░░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ⅜ ‾‾3 ░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░‾‾2⅝ 2¼ ░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░ ░░░░░░ ░░░░░░░░░░░░░ ░░░░░░░░░░░░ ░░░ ⅜ _____________________________________‾‾⅜ ‾‾0
With their programs in hand, the Elves checked out the chimneys and Santa made it through another holiday season without any injuries.
my @bricks = [‘ok’, ‘split’, ‘ok’, ‘ok’, ‘split’, ‘—-‘];
# Nice article :-)
# .wrap and .unwrap (or .restore) and scope
# (hopefully a useful addendum :-)
# note: chars ‘_’ (in ^find_method), and ‘/’ (between ‘….}{….’ blocks) aren’t entirely visible in the article
say “two elves debated whether each brick should be assessed for Santa’s safety”;
my @bricks = <ok split ok ok split ==== >;
for @bricks { .Str.say };
my $elf1 = Str.^find_method(‘Str’).wrap:
sub ($s) {
return $s ~~ ‘split’ ?? ‘no problem’ !! $s
}
for @bricks { .Str.say };
my $elf2 = Str.^find_method(‘Str’).wrap:
sub ($s) {
return $s ~~ ‘split’ ?? ‘very scary’ !! $s
}
for @bricks { .Str.say };
# $elf2.restore;
# or
Str.^find_method(‘Str’).unwrap($elf2);
for @bricks { .Str.say };
# $elf1.restore;
# or
Str.^find_method(‘Str’).unwrap($elf1);
for @bricks { .Str.say };
say “Santa’s not overly worried about the brick condition”;