Perl Weekly Challenge 31: Illegal Division by Zero and Dynamic Variables

These are some answers to the Week 31 of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few days from now (October 27, 2019). This blog post offers some solutions to this challenge, please don't read on if you intend to complete the challenge on your own.

Challenge # 1: Illegal Division by Zero

Create a function to check divide by zero error without checking if the denominator is zero.

Illegal Division by Zero in Perl 5

Perl 5 has a number of modules to implement exception handling using the try ... catch pattern found in a number of other languages, such as, for example, Try::Tiny or TryCatch. Even Autodie might actually fit the bill. But, because this is a coding challenge, we prefer to avoid using modules that do the work you're supposed to do.

In fact, not doing anything special will check the division by zero, as shown in these one-liners:

$ perl -E 'say $ARGV[0] / $ARGV[1]' 2 4
0.5

$ perl -E 'say $ARGV[0] / $ARGV[1]' 2 0
Illegal division by zero at -e line 1.

In a way, this simple one-liner does check the divide by zero error. But, OK, maybe that's cheating a bit. So let's try to catch an error in this case.

Since I don't want to use modules here, I'll use the good old eval built-in function. The eval function has two forms (string and block). The string version is sometimes frowned upon for various reasons (notably because it can be dangerous when used carelessly), but it can be very useful when properly used. Anyway, we'll be using here the block version that doesn't have such problems. This form is typically used to trap exceptions (which is what we need here), while also providing the benefit of checking the code within the eval block at compile time.

If there is a syntax error or runtime error, eval returns undef in scalar context, or an empty list in list context, and $@ is set to the error message. (Prior to 5.16, a bug caused undef to be returned in list context for syntax errors, but not for runtime errors. We're using here version 26 of Perl, so we don't care about this former bug which would not have annoyed us anyway in our context.) If there was no error, $@ is set to the empty string.

This is our program:

#!/usr/bin/perl
use strict;
use warnings;
use feature qw/say/;

die "We need an input of two numbers\n" unless @ARGV == 2;
my ($numerator, $denominator) = @ARGV;
my $result;
eval {
    $result = $numerator / $denominator;
};
die "$@" if $@;
say "Result of division is: $result";

Running this program with various input values produces the following output:

$ perl illegal_div.pl
We need an input of two numbers

$ perl illegal_div.pl 32
We need an input of two numbers

$ perl illegal_div.pl 32 8
Result of division is: 4

$ perl illegal_div.pl 32 0
Illegal division by zero at illegal_div.pl line 10.

Illegal Division by Zero in Perl 6 (or Raku)

Perl 6 has very rich error handling features, most notably the Exception class. Without going into all the lengthy details, let us say that it's possible to handle exceptional circumstances by supplying a CATCH block. To solve the challenge can be as simple as this:

use v6;

sub MAIN (Numeric $numerator, Numeric $denominator) {
    say "Result of division is: ", $numerator / $denominator;
    CATCH {
        say $*ERR: "Something went wrong here: ", .Str;
        exit; 
    }
}

Using this script first with legal parameters and then with an illegal 0 denominator produces the following output:

$ perl6 try-catch.p6  8 4
Result of division is: 2

$ perl6 try-catch.p6  8 0
Something went wrong here: Attempt to divide by zero when coercing Rational to Str

An exception object is usually contained in the $! special variable, but a CATCH block topicalizes the exception object, meaning that it becomes available in the $_ topical variable (hence the .Str syntax is sufficient to obtain the description of the exception).

Although it is not really needed here, it may sometimes be useful to define the scope of the CATCH block by enclosing it in a try block, for example:

use v6;

sub MAIN (Numeric $numerator, Numeric $denominator) {
    try {
        say "Result of division is: ", $numerator / $denominator;
        CATCH {
            say $*ERR: "Something went wrong here: ", .Str;
            exit; 
        }
    }
}

Actually, defining a try block creates an implicit CATCH block, and this may be used to contain the exception:

use v6;

sub MAIN (Numeric $numerator, Numeric $denominator) {
    try {
        say "Result of division is: ", $numerator / $denominator;
    }
}

The above program does not die and doesn't print anything but exits normally (with the successful exit code, 0, on Unix-like systems) when you pass a zero value for the denominator. We're in effect silencing the exception. Even if you don't want to abort the program when encountering such an error, you might still prefer to tell the user that something went wrong with a message containing the description of the caught exception:

use v6;

sub MAIN (Numeric $numerator, Numeric $denominator) {
    try {
        say "Result of division is: ", $numerator / $denominator;
    } or say "Houston, we've had a problem here: ",  $!.Str;
}

which outputs the following:

$ perl6 try-catch.p6  8 4
Result of division is: 2

$ perl6 try-catch.p6  8 0
Houston, we've had a problem here: Attempt to divide by zero when coercing Rational to Str

Dynamic Variable Name

Create a script to demonstrate creating dynamic variable name, assign a value to the variable and finally print the variable. The variable name would be passed as command line argument.

There are some scripting languages (such as various Unix shells) where it is possible to dynamically create variable names. This is sometimes useful, but it tends to mess up the script's name space.

If I understand the task well, we're requested to use symbolic references, as this is probably the only way to dynamically create variable names in Perl. I must warn you: Symbolic references are evil. Don't use them in Perl. There are better ways, as we will see later.

I could spend quite a bit of time explaining why they are bad, but Mark-Jason Dominus, the author of Higher Order Perl (an excellent book BTW), has already done it much better that I could probably ever do. Please read Mark-Jason's article in three installments on the subject:

Dynamic Variable Name in Perl 5

If you're using use strict; (and you always use this pragma, right?), you will not be able to use symbolic references and you'll get this type of error:

Can't use string ("foo") as a SCALAR ref while "strict refs" in use at sym_ref.pl line 12.

There are good reasons for that. Symbolic references were quite common in Perl 4 (which was replaced by Perl 5 more than 25 years ago) and they have not been removed completely from Perl 5 in the name of backward compatibility. That's why it is still possible (but highly deprecated) to use them. But they are never needed in Perl 5. With the strict pragma, you're forbidden to use symbolic references since it can be safely assumed that you're not using Perl 4 and they are bad in Perl 5. You'll need to disable part of the Strict benefits to use symbolic references.

In other words, the first thing you need to do if you really want to shoot yourself in the foot and use symbolic references is to disable strict references with the pragma no strict 'refs'; (but again, you really shouldn't be doing that). I hate to have to show this, but, then you can (but really shouldn't) use the variable name as a variable:

#!/usr/bin/perl

use strict;
use warnings;
use feature 'say';
# DON'T DO THIS
no strict 'refs'; # Needed for allowing symbolic references

@ARGV == 2 or die "Please supply two parameters.";
my ($var_name, $var_content) = @ARGV;

# This is bad, don't do it!
$$var_name = $var_content;
say "The variable is called $var_name and its value is $$var_name";

And this works as expected:

$ perl sym_ref.pl foo bar
The variable is called foo and its value is bar

But it is dangerous, for the reasons well explained by Mark-Jason Dominus. Sometimes, I say on forums things like this: "You should probably not use subroutines prototypes unless you really know what you're doing." In the case of symbolic references, I would be more adamant: don't do that, probably even if you think you really know what you're doing.

Note that we have used the eval function in the first task of this challenge. It is also be possible to create dynamic variable names using eval (without using no strict 'refs';), but it is IMHO at least as bad, perhaps even worse, so don't do this either. We will see now how to obtain the same result cleanly.

A Much Better Solution

If you're using symbolic references, you are in fact messing under the hood with a quite special global hash, the symbol table or a lexical pad. It is always far better to use a regular lexical hash (or sometimes a hash of hashes). For example, in our case, a very simple hash:

#!/usr/bin/perl

use strict;
use warnings;
use feature 'say';


@ARGV == 2 or die "Please supply two parameters.";
my %hash;
my ($name, $content) = @ARGV;
$hash{$name} = $content;
say "The item is called $name and its value is $hash{$name}";

This program displays the name of the item and its value:

$ perl sym_ref_fixed.pl foo bar
The item is called foo and its value is bar

Dynamic Variable Name in Perl 6 (Raku)

I do not think that there is anything like symbolic references in Perl 6. So, it seems that it is not possible to literally "demonstrate creating dynamic variable name" in Perl 6. What we can do, however, is to replicate the much better P5 solution and use a hash:

use v6;

sub MAIN (Str $name, Str $value) {
    my %hash = $name => $value;
    say "The item is called $name and its value is %hash{$name}";
}

This program displays the name of the item and its value:

$ perl6 sym_ref.p6 foo bar
The item is called foo and its value is bar

Wrapping up

The next week Perl Weekly Challenge is due to start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on Sunday, November, 3. And, please, also spread the word about the Perl Weekly Challenge if you can.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.