The Everloving Perl's Constant - Part 1

Constant in Perl is a pain in the arse to use rather than not use. It took me quite a while to figure this out. And again, Perl's TMTOWTDI strikes again.

Following this question asked in SO, let use this example as shown below.
package FooBar;

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

use constant HTTP_OK => '200';
use constant COLUMNS => [qw/ TEST1 TEST2 TEST3 /];

say @{+COLUMNS};

1;

Since `COLUMNS` is a constant reference to an array of three elements, to fetch values from the constant, we must dereference it first through the `@{}` sigil.

However, there is a caveat here. In Perl, compare to other programming languages, a constant is not really a constant in normal convention. Rather, it's a prototype functions or a special kind of subroutine. If you use constant in the context that automatically quote barewords, in this case, dereferencing an array, `COLUMNS` may be interpreted as string and causing compilation errors as shown below.

say @{COLUMNS};

Ambiguous use of @{COLUMNS} resolved to @COLUMNS at test.pl line 11.
Variable "@COLUMNS" is not imported at test.pl line 11.
        (Did you mean &COLUMNS instead?)
Global symbol "@COLUMNS" requires explicit package name (did you forget to declare "my @COLUMNS"?) at test.pl line 11.
Execution of test.pl aborted due to compilation errors.


Thus, to prevent this, we need to tell Perl to ignore automatic quoting. How, there are two ways. First, use a special unaray symbol, like plus or ampersand sign as shown. Second, call the constant as a Perl subroutine.
say @{+COLUMNS};  # first way, plus sign
say @{&COLUMNS};  # first way, ampersand sign
say @{COLUMNS()}; # second way

Similarly, if we want to use a constant in hash key, we have to prevent the automatic bareword quoting. There are many ways.
%response = (HTTP_OK() => 1);
%response = (+HTTP_OK => 1);
%response = (&HTTP_OK => 1);
$response{HTTP_OK()} = 1;
$response{+HTTP_OK} = 1;
$response{&HTTP_OK} = 1;

Not using fat arrow (=>) but plain comma.
%response = (HTTP_OK, 1);

Alternatively, instead of using array reference, we can declare the constant as list instead.
use constant COLUMNS => qw/ TEST1 TEST2 TEST3 /;

Since the `COLUMNS` constant is a list context, assessing it should be placed in the parenthesis. And calling it as Perl sub works as well.
say (COLUMNS); # first way
say COLUMNS(); # second way

Comparing between two ways, initialization using list is more readable as we do not need to dereference the constant. For example, when looping through the constant.
foreach (COLUMNS) {
    say $_;
}

Instead of dereferencing.
foreach (@{+COLUMNS}) {
    say $_; 
}

Interesting right? That's the way of Perl's TMTOWTDI.

No comments:

Post a Comment