This is my response to the Perl Weekly Challenge #031.
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.
See docs.raku.org/type/Rat for more information about «Rat».
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).
See
docs.raku.org/type/Num for more
information about the floating point Num
type.
Now, back to the challenge. We can wrap the say line in a
try
block,
like this:
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.
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' 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.
::()
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.