Perl 6 Small Stuff #20: From anonymous one-line functions to full length MAINs with type and error checking

Image for post
Image for post

There’s been a few weeks where I haven’t followed Perl Weekly Challenge, but luckily I found some time for Challenge #11 this week. Inititally both exercises looked they quite a bit of coding. But after a while it became apparaent that both could be solved with relatively simple one (or two) liners.

But I also found that one-liners aren’t always very robust when it comes to error handling, so that gave me an opportunity to explore Perl6’s built-in type (and content) checking of command line input parameters, as well as how to use Perl 6’s built-in methods of generating friendly and readable Usage output.

But before all of that we’ll start with the second excersise.

Write a script to create an Indentity Matrix for the given size. For example, if the size is 4, then create Identity Matrix 4x4.

I admit that I really don’t know what an Identity Matrix is or why it’s important, but the challenge provided a link to the Wikipedia page so that I’d at least understand how one would look. The look, simply told, is a square matrix where the main diagonal is filled with 1’s and the rest is filled with 0's.

There are Perl 6 modules that are helpful, but I though I’d write a solution from the bottom up. Given the many cool features of Perl 6, generating an identity matrix is one line of code:

my &idm = -> $s { gather for ^$s -> $y { take map { Int($_ == $y) }, ^$s } };

Call the function like this: my @identity-matrix = idm(4); — and an array containing the identity matrix for your given size is returned.

Note the & sigil. This is the sigil for Code, just as @ is for arrays and % is for hashes. This code utilises Perl 6’s anonymous functions. I could have written this as an ordinary sub, but in the context of a one-liner I think this functional variant looks better: -> $s defines an anonymous function with one parameter ($s).

The interesting thing with the code sigil is that it’s optional when you call it. So you can call it both like my @id-mat = idm(4) and my @id-mat = &idm(4) . I think the former looks better.

As you’ll have seen many times before on this blog I once again use the gather… take… functionality, which gives me the ability to easily build an array (I could have used push on an array too, but again — I feel gather-take looks better and is more concise).

^$s generates a list from 0 to the requested size, and the for loops trough that list. For row number one I loop through ^$s once more, and use map populate the identity matrix. If row number and column number is the same, the value is set to 1. If not 0. Instead of using an if sentence to check and set this, I combine everything into one with Int($_ == $y). $_ == $y returns True or False; Int() casts this into 1 or 0.

The end result is an array of arrays containing your identity matrix.

I could have stopped there, but since this is a Perl Weekly Challenge, one I thought I should prove that the function works. I do that by adding a second line of code that prints creates and prints a matrix:

.join(' ').say for $idm(4);

The output is:

1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

That’s it. Short and simple.

Having solved this second excersise, I returned to the first. That one looked like it’d need more code.

Write a script that computes the equal point in the Fahrenheit and Celsius scales, knowing that the freezing point of water is 32 °F and 0 °C, and that the boiling point of water is 212 °F and 100 °C.

I guess you could explain equal points in many ways. I think of it as the point where two lines cross. In this case: What’s the point where the celcius # and the fahrenheit # is identical and indicates the same absolute temperature?

Before we begin: The answer is 40 below zero (-40). But how do we compute this easily? We have to use the formula for conversion. If x represents the temperature in celcius, fahrenheit — y — is calculated like this:

y = 9/5*x + 32

When you look at it like this you’ll see that this looks exactly like a formula for a line, the y = mx + b we know from school — where m is the gradient or the slope of the line and b the y intercept, i.e. the value of y when x = 0. In this case m = 9/5 and b = 32.

The celcius scale is easy — y is equal to x always, so the formula looks like this:

y = x

To calculate the equal point we have to find the value of x where y is the same for both formulas. We can build the equation for solving this by taking the right sides of the equation for fahrenheit and celcius lines and put them together like this…

(the right hand side of y = x) = (the right hand side of y = 9/5 * x + 32)
x = 9/5 * x + 32

…and solve the equation. Since 9/5 is 1.8, let’s use that instead. So…

x = 1.8x + 32
x-1.8x = 32 # or 1.0x-1.8x = 32
-0.8x = 32 # -0,8x because 1.0–1,8
x = 32 / -0.8
x = -40

Now we know enough to not only solve a comparision between celcius and fahrenheit, but between celcius and whatever scale you’d throw at it. We have to implement x = 32 / -0.8 in some way. Or, figure out a way to make -0.8 generic, i.e. put in any scale and get this to work.

Here’s the key: the grade 9/5 or 1.8 is really just the fahrenheit degrees for the boiling point of water subtracted by the same for the freezing point, and then divided by the same for celcius (which happens to be 100). I.e. (212-32)/100 = 9/5 = 1.8. So if we have the freezing and the boiling point of any temperature scale other than celcius itself, the grade for that scale is:

(freezing point-boiling point) / 100

Now, if we peek at on of the steps of the calculation above, 1.0x-1.8x = 32, we see that vi have to subtract the grade from 1.0, so that we end up with the final

y-intercept (or freezing point) / 1.0-grade

…or more verbosely

freezing point / (1.0-((freezing point-boiling point) / 100))

If we assume that we have a variable $a for the freezing point and a variable $b for the boiling point, a Perl 6 one-liner would look like this:

-> $a, $b { say "Equal point: " ~ ( $b - $a == 100 ?? "None" !! $a / (1 - (($b - $a) / 100))) }(32,212);

Substitute 32, 212 with anything you’d like. Let’s say you invented a temperature scale where 20 is freezing and 80 is boiling, swap (32,212) with (20,80). The result? 50.

Quick and dirty — but effective. However the code above will crash under certain circumstances, and in many scenarios calculate that the equal point is at a temperature that simply does not exists (temperatures below absolute zero). So I thought that I’d implement this with a little error handling as well. And I think that version showcase some Perl 6 greatness that I haven’t touched upon earlier:

File: eqpoint.p6
Run: ./eqpoint.p6 32 212
./eqpoint.p6 fahrenheit
....................#!/usr/bin/env perl6multi MAIN( #= Compares the C. scale to any scale you dream up
Real $f, #= freezing point in custom scale
Real $b #= boiling point in custom scale
) {
say "There is no equal point for this scale." and exit if $b - $f == 100;
my $equal-point = $f / (1 - (($b - $f) / 100));
say "The calculated equal point is only theoretical as it is below absolute zero." if $equal-point < -273.15;
say "Equal point: $equal-point";
multi MAIN( #= Compares the C. scale to a named temperature scale
Str $scale where { $_ ~~ m:i/^(fahrenheit|kelvin|rankin)$/ } #= Name of scale (Fahrenheit, Kelvin or Rankin)
) {
given $scale.fc {
when "fahrenheit" { MAIN( 32 , 212 ); }
when "kelvin" { MAIN(273.15, 373.15); }
when "rankin" { MAIN(491.67, 671.67); }

The use of the MAIN subroutines gives us some command line parsing for free. Not only that, but Perl 6 does type checking for us — in the case of the first multi MAIN, it ensures that the two parameters it receives actually are numbers. The second multi MAIN looks only for a single string, and only three strings in particular: fahrenheit, kelvin and rankin.

The program won’t run if you try to start it with any other parameters. Instead it spits out a Usage text like this:

eqpoint.p6 <f> <b> -- Compares the Celcius scale to whatever scale you dream up
eqpoint.p6 <scale> -- Compares the Celcius scale to a given temperature scale

<f> freezing point in this scale
<b> boiling point in this scale
<scale> Name of scale (Fahrenheit, Kelvin or Rankin)

You’ll see that this custom usage text is caused by my use of #= in the code above. #= behind the sub routine name is the generic description of the usage. Behind parameter variables they describe that particular parameter.

Now, when calculating the grade I do some value checking to avoid certain scenarios. First I check whether the difference between boiling and freezing in the user’s custom sale equals 100. If it does, that scale runs in parallell with the celcius scale. That means that the lines never cross and there is no equal point. If that’s the case I print out a message about that and exit. (The equation would have cause a divide by zero error had I move on)

In addition I check whether the calulcated equation point is below -273.15 celcius. If it is, the point is below absolute zero and would never ever occur in this universe. In those scenarios I point out that the equal point is only theoretical.

I think it’s extra fun when the challenges, well, challenge me to explore little-used areas of Perl 6. I got to do lots of that today. If you haven’t tried your hand at the Perl Weekly Challenge yet, I highly recommend it.

Written by

Norwegian with many interests. Programming being one of them.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store