Dynamic Zero with Raku

by Arne Sommer

Dynamic Zero with Raku

Published 24. October 2019

This is my response to the Perl Weekly Challenge #31.

Challenge 31.1

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

First a little show off. Division by zero isn't allowed, as any decent computer science student will be happy to tell you. The reason is quite simple. Multiplication and division are related, actually reverse operations:

15 / 3 = 5   ⇆   3 * 5 = 15
50 / 5 = 10  ⇆   5 * 10 = 50

You get the idea. Now try with zero:

12 / 0 = x   ⇆   0 * x = 12

The last equation can be shortened to 0 = 12, which obviously isn't true.

Computers choose to crash a program that tries to divide by zero, right?

Well. Not entirely. You can get away with this in Raku:

File: zero-deferred:
my $a = 10 / 2;
my $b = 20 / 0;
my $c = 30 / 11;

say "Ok";

Running it:

$ raku zero-deferred
Ok

The reason is that Raku treats division by zero as a deferred error. It is an error, but the consequence is deferred to the time when you actually use the value. Printing it does the trick:

File: zero-error:
my $a = 10 / 2;
my $b = 20 / 0;
my $c = 30 / 11;

say "Ok";

say $a;
say $b;
say $c;

Running this one fails:

$ raku zero-error
Ok
5
Attempt to divide by zero when coercing Rational to Str
  in block <unit> at zero-error line 10

The compiler creates a rational number (the Rat type), and it is the stringification caused by the «say» statement that causes the error to terminate the program.

A little REPL work can be illuminating:

$ raku
> my $a = 2/0; 
Attempt to divide by zero when coercing Rational to Str

> my $b = 2/0; say "l"
l
> $b.WHAT
(Rat)

> my $c = pi/0; say "l"
l
> $c.WHAT
(Failure)

The say "l" part is there to prevent mayhem. In REPL-mode the value of the last expression is printed out. This is done by evaluating the value, and that causes the program to crash. The first error message actually says so: «coercing Rational to Str».

As long as the value we try to divide by zero is an integer, we get a Rational number. If we pass it something that isn't, we get the error up front as Raku has to compute the value (usually as a floating point number; the Num type).

Now, back to the challenge. We can wrap the say line in a try block, like this:

File: zero-try
unit sub MAIN (Numeric $a = 10, Numeric $b = 0);

my $c = $a / $b;

try say "a/b = $c";

say "Division by zero detected." if $!;

Running it:

$ raku zero-try 2 11
a/b = 0.181818

$ raku zero-try 2 0
Division by zero detected.

If you want the program to recover, do something with «$c» before accessing it again. E.g.:

  $c = 0 if $!; # Instead of the 'say' line above.

But you probably shouldn't, as it would be a grave error in most cases.

Challenge 31.2

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.

It is dangerous to allow creation of variables on the fly based on user input. (Do not mention PHP.) It is possible to specify special (system) variables, and really screw things up.

It is possible to do this in Perl 5, but I will not show how. Do read Why it's stupid to `use a variable as a variable name' before contemplating it. The article is from 1998, but the point is equally valid today.

A hash is a much better alternative. Like this:

File: dynavar-hash
unit sub MAIN ($name, $value);

my %store;

say "Name: %store{$name}";

Feel free to consider it cheating. But you'll sleep better at night.

Lookup Only

In Raku you cannot create a variable dynamically (and thank you to the developers for that decision), but we can access an already existing one with the ::() operator:

File: dynavar
unit sub MAIN ($name = '$a');

my $a = 12;
my $b = 15;
my $c = 19;
my $d = 26;
my $e = 99;

say "The value of $name: " ~ ::($name);

Running it:

$ raku dynavar 
The value of $a: 12

$ raku dynavar '$b'
The value of $b: 15

$ raku dynavar '$f'
No such symbol '$f'
  in sub MAIN at dynavar line 11
  in block <unit> at dynavar line 1

I cannot really see any use case, but the possibility is there.

See docs.perl6.org/language/packages#index-entry-::() for more information about the ::() operator.

And that's it.