Showing posts with label tmtowtdi. Show all posts
Showing posts with label tmtowtdi. Show all posts

The Everloving Perl's Constant - Part 2

In the previous post, we've look into declaring and using constant within the same module. How about using a constant from other package?

Let's create a sample `FooBar` module ( which exports a constant, `COLUMNS` as shown below.
package FooBar;

use strict;
use warnings;
use Exporter qw(import);

use constant COLUMNS => qw(TEST1 TEST2 TEST3);


To use this in another Perl script ( Notice there are several ways to use and access the `COLUMNS` constant.
use FindBin; # current script path
use lib "$FindBin::Bin"; # append current script path to the Perl's library path
use FooBar 'COLUMNS';

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

say FooBar::COLUMNS();
say FooBar->COLUMNS();
say FooBar::COLUMNS;
say (COLUMNS);
say (COLUMNS());


Should we use constant pragma in Perl? No, according to Damian Conway in this book, Perl Best Practices. Instead he advocates that we should "use named constants, but don't use constant."  What he meant if you want to use constant in Perl, use the Readonly module instead of the constant pragma. However, Perl Best Practices was written in 2005, 13 years ago. Hence, which begs the question, does the recommend practices still relevant for these days? Nope, even Modern Perl 4th Edition, published in 2016 also discourages the use of bareword constant.

So, if we still want to use constant in Perl, what is the modern take (as in 2018) on Perl's constant? Discussion at Reddit leads to two Perl modules, Const::Fast or ReadonlyX. I like the former for its speed (even though benchmark results doesn't differ much) and simplicity as shown below (example taken from the module doc).
use Const::Fast;
const my $foo => 'a scalar value';
const my @bar => qw/a list value/;
const my %buz => (a => 'hash', of => 'something');

I've read and seen developers who work around this problem by using  scalar through `our`and uppercase to declare variables.

Some even encapsulate these constants within a subroutine (which is true anyway as constant in Perl is an anonymous subroutine) with predefined values.
    return qw(TEST1 TEST2 TEST3);

Nothing wrong with that as there are developers who don't even want to bother with Perl's caveat. Furthermore, benchmark shows that these two methods, using variable or subroutine, is faster than using constant pragma. Maybe they are right. Perl, a weakly-typed and TMTOWTDI programming language, should be "abused" and treated like one.

In the end, should we even bother? Yes. After the comparison of 21 Perl modules that implement constant (yes, 21 bloody ways and that was written in 2012), it's good to stick to a standard way of using constant in your code base. Be it a constant pragma or Const::Fast module, just stick to one.

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};


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 line 11.
Variable "@COLUMNS" is not imported at line 11.
        (Did you mean &COLUMNS instead?)
Global symbol "@COLUMNS" requires explicit package name (did you forget to declare "my @COLUMNS"?) at line 11.
Execution of 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.