The Pearls of Raku, Issue 4: unit sub MAIN and command line, round and precision

Welcome to the next issues if The Pearls of Raku! Today, some interesting findings that I discovered while reviewing the previous weeks of The Perl Weekly Challenge and today when I added a new graph to The Coronavirus Observer.

Welcome to the next issues if The Pearls of Raku! Today, some interesting findings that I discovered while reviewing the previous weeks of The Perl Weekly Challenge and a thing that I used for the first ever time when I added a new graph to The Coronavirus Observer.

Table of Contents

unit sub MAIN

The unit sub MAIN construct makes the rest of the file the body of the MAIN function. In other words, it gives a handy way to declare parameters of your program and to restrict its values.

Imagine a command-line calculator that you run as:

$ ./calc.raku 42 + 5

You want to make sure that the first and the third arguments are integers, and there is a sign of the operation in between.

Here is a possible solution in Raku (maybe a bit exaggerated just for the sake of demonstration).

my Int $a = +@*ARGS.shift;
subset OpStr of Str where /<[-+*/]>/;
my OpStr $op = @*ARGS.shift;
my Int $b = +@*ARGS.shift;

say (given $op {
    when '+' {$a + $b}
    when '-' {$a - $b}
    when '*' {$a * $b}
    when '/' {$a / $b}
})

The highlighted part is where you get the arguments and indirectly validate them.

If you convert the code to a function, you can use function’s signature to check the arguments for you. In the case of a simple script with the only function, use unit sub MAIN:

unit sub MAIN(Int $a, Str $op where /<[-+*/]>/, Int $b);

say (given $op {
    when '+' {$a + $b}
    when '-' {$a - $b}
    when '*' {$a * $b}
    when '/' {$a / $b}
})

Also notice the difference in error reporting between these two programs. In the first case, an exception happens:

$ raku calc1.raku 42 plus 3
Type check failed in assignment to $op; expected OpStr but got Str ("plus")
  in block <unit> at calc1.raku line 3

In the second case, you get a usage message:

$ raku calc2.raku 42 plus 3
Usage:
  calc2.raku <a> <op> <b>

P. S. Note that you can only use the trick with MAIN. For other functions, you get an error:

A unit-scoped sub definition is not allowed except on a MAIN sub;
Please use the block form. If you did not mean to declare a unit-scoped sub,
perhaps you accidentally placed a semicolon after routine's definition?

round

An interesting thing you may skip in the documentation is that the round routine takes an optional scale parameter. You can use it to set the number of digits after the decimal point, for example.

Round the value of π:

say pi.round; # 3

Now, print one, two, or three digits after the decimal point:

say pi.round(0.1);   # 3.1
say pi.round(0.01); # 3.14
say pi.round(0.001); # 3.142

But that’s not all! Do you want to get only an odd number as a result? Round the value to a multiple of 2:

say pi.round(2); # 4

Set the scale to 5 or 10 and round it to a number which is a multiple of these numbers:

say pi.round(5);  # 5
say pi.round(10); # 0

You can work with big non-round numbers too. Say, let’s round the World population to see billions only:

my $population = 7_802_279_546;
say $population.round(1_000_000_000); # 8000000000

And that’s it for now. You can find the source codes of this issue on GitHub.

Leave a Reply

Your email address will not be published. Required fields are marked *