DEV Community

Jonas Brømsø
Jonas Brømsø

Posted on • Edited on

Blog post: Perl's Constant Pragma and Readonly Module

The other day I was doing a code review of of some Perl code and I fell over a magic number. I pointed it out to the author, emphasizing the concept of using a constant pragma instead of a regular mutable variable as recommended by the Perl::Critic policy: Perl::Critic::Policy::ValuesAndExpressions::ProhibitMagicNumbers

He changed the code accordingly to use a constant, but copied the value of the constant into a regular variable so it could be used in string interpolation, stating that the use of constants in interpolated strings is ugly.

I completely agree, but the proposed solution did not make much sense either.

This is an example, so you can visualize the code (this is not the actual code):

use v5.10;

use constant TRUTH => 42;

my $truth = TRUTH;

say "The truth is $truth";
Enter fullscreen mode Exit fullscreen mode

A nice constant and proper string interpolation using a regular variable. And the alternative demonstrating the use of the constant in conjunction with the string output:

use v5.10;

use constant TRUTH => 42;

say 'The truth is '.TRUTH;
Enter fullscreen mode Exit fullscreen mode

I recommended use of Readonly instead.

use v5.10;

use Readonly;

Readonly::Scalar my $truth => 42;

say "The truth is $truth";
Enter fullscreen mode Exit fullscreen mode

This gives us the best of both worlds:

  • We have an immutable variable
  • We can use it in a string interpolation

Readonly comes with additional benefits, so if you by accident change the immutable variable your Perl application dies with the error (your application name and line number might vary).:

Modification of a read-only value attempted at immutable.pl line 11
Enter fullscreen mode Exit fullscreen mode

Example:

#!/usr/bin/env perl

use strict;
use warnings;
use v5.10;

use Readonly;

Readonly::Scalar my $truth => 42;

$truth = 1;

say "The truth is $truth";
Enter fullscreen mode Exit fullscreen mode

The policy Perl::Critic::Policy::ValuesAndExpressions::ProhibitMagicNumbers recommends the use of the constant pragma or Readonly, but another related Perl::Critic policy is not recommending the use of the constant pragma, namely: Perl::Critic::Policy::ValuesAndExpressions::ProhibitConstantPragma. It's wide use easily demonstrates the uglyness also as described in the examples above and in addition the the documentation in Readonly makes compelling case. I have however always enjoyed Perl's constant pragma. I wrote about this along time ago and the actual write-up is no longer available and got lost at some point.

In order to explain it is time to redo the write-up. The constant pragma has one key benefit and that is the Perl compiler can optimize based on this. Let me demonstrate:

#!/usr/bin/env perl

use strict;
use warnings;
use v5.10;

use constant DEBUG => 1;

if (DEBUG) {
    print STDERR "Hello Happy World of Debugging\n";
}

exit 0;
Enter fullscreen mode Exit fullscreen mode

Let the compiler back-end (B) and it's companions (B::Terse) do their bidding:

$ perl -MO=Terse constant_optimization.pl
LISTOP (0x7ff13f15cbe8) leave [1]
    OP (0x7ff13f15cb00) enter
    COP (0x7ff13f15cc30) nextstate
    LISTOP (0x7ff13f15cc90) scope
        COP (0x7ff13f15ccd8) null [193]
        LISTOP (0x7ff13f15cd38) print
            OP (0x7ff13ec0c8f8) pushmark
            UNOP (0x7ff13f15cdc8) rv2gv
                SVOP (0x7ff13ec0c8a0) gv  GV (0x7ff13f02ac38) *STDERR
            SVOP (0x7ff13f15cd80) const  PV (0x7ff13f8d0ab8) "Hello Happy World of Debugging\n"
    COP (0x7ff13f15cb48) nextstate
    UNOP (0x7ff13f15cba8) exit
        SVOP (0x7ff13ec0c938) const  IV (0x7ff13f8d0a70) 0
constant_optimization.pl syntax OK
Enter fullscreen mode Exit fullscreen mode

But if set the constant to false (0):

> perl -MO=Terse constant_optimization.pl
LISTOP (0x7fac8f504b30) leave [1]
    OP (0x7fac8f89d780) enter
    COP (0x7fac8f89d6d8) null [193]
    OP (0x7fac8f504bc8) null [5]
    COP (0x7fac8f89d630) nextstate
    UNOP (0x7fac8f89d738) exit
        SVOP (0x7fac8f89d7c8) const  IV (0x7fac900554b8) 0
constant_optimization.pl syntax OK
Enter fullscreen mode Exit fullscreen mode

The product is only 7 lines, the output of Hello Happy World of Debugging string is optimized out, where as the first version is 13 lines and includes the statements wrapping in our flow-control relying on the constant's value.

If we repeat the exercise using Readonly:

#!/usr/bin/env perl

use strict;
use warnings;
use v5.10;

use Readonly;

Readonly::Scalar my $DEBUG => 0;

if ($DEBUG) {
    print STDERR "Hello Happy World of Debugging\n";
}

exit 0;

Enter fullscreen mode Exit fullscreen mode

Firstly we have with the immutable variable set to 0

$ perl -MO=Terse readonly_optimization.pl
LISTOP (0x7fc797820e60) leave [1]
    OP (0x7fc797821df0) enter
    COP (0x7fc797820ea8) nextstate
    UNOP (0x7fc797820f08) entersub
        UNOP (0x7fc797820f80) null [158]
            OP (0x7fc797820f48) pushmark
            OP (0x7fc795c0c938) padsv [1]
            SVOP (0x7fc797820fc8) const  IV (0x7fc7970b16e0) 0
            UNOP (0x7fc795c0c8a0) null [17]
                SVOP (0x7fc795c0c8f8) gv  GV (0x7fc797895018) *Readonly::Scalar
    COP (0x7fc797821f20) nextstate
    UNOP (0x7fc797821f80) null
        LOGOP (0x7fc797821fc0) and
            OP (0x7fc797820e28) padsv [1]
            LISTOP (0x7fc797820c30) scope
                COP (0x7fc797820c78) null [193]
                LISTOP (0x7fc797820cd8) print
                    OP (0x7fc797820da8) pushmark
                    UNOP (0x7fc797820d68) rv2gv
                        SVOP (0x7fc797820de8) gv  GV (0x7fc79602ac38) *STDERR
                    SVOP (0x7fc797820d20) const  PV (0x7fc7970b19c8) "Hello Happy World of Debugging\n"
    COP (0x7fc797821e38) nextstate
    UNOP (0x7fc797821e98) exit
        SVOP (0x7fc797821ed8) const  IV (0x7fc7970b1a28) 0
readonly_optimization.pl syntax OK
Enter fullscreen mode Exit fullscreen mode

Secondly we have with the immutable variable set to 1

> perl -MO=Terse readonly_optimization.pl
LISTOP (0x7fcce402e060) leave [1]
    OP (0x7fcce402e7f0) enter
    COP (0x7fcce402e0a8) nextstate
    UNOP (0x7fcce402e108) entersub
        UNOP (0x7fcce402e180) null [158]
            OP (0x7fcce402e148) pushmark
            OP (0x7fcce3d09f48) padsv [1]
            SVOP (0x7fcce402e1c8) const  IV (0x7fcce512fef8) 1
            UNOP (0x7fcce3d09eb0) null [17]
                SVOP (0x7fcce3d09f08) gv  GV (0x7fcce5237418) *Readonly::Scalar
    COP (0x7fcce402e920) nextstate
    UNOP (0x7fcce402e980) null
        LOGOP (0x7fcce402e9c0) and
            OP (0x7fcce402e028) padsv [1]
            LISTOP (0x7fcce402de30) scope
                COP (0x7fcce402de78) null [193]
                LISTOP (0x7fcce402ded8) print
                    OP (0x7fcce402dfa8) pushmark
                    UNOP (0x7fcce402df68) rv2gv
                        SVOP (0x7fcce402dfe8) gv  GV (0x7fcce4822838) *STDERR
                    SVOP (0x7fcce402df20) const  PV (0x7fcce51301c8) "Hello Happy World of Debugging\n"
    COP (0x7fcce402e838) nextstate
    UNOP (0x7fcce402e898) exit
        SVOP (0x7fcce402e8d8) const  IV (0x7fcce5130228) 0
readonly_optimization.pl syntax OK
Enter fullscreen mode Exit fullscreen mode

The output is very similar and no optimization is made, the value for the flow-control part is visible as either 0 or 1, depending on what listing you are reading, but apart from that they are identical in number of lines and contents.

Conclusion: when you have parts of your code which could benefit from immutability, like magic numbers, use Readonly, When you however want to benefit from the compiler optimization use the constant pragma.

But as always with Perl, There Is More Than One Way To Do It and Perl::Critic::Policy::ValuesAndExpressions::ProhibitMagicNumbers also points to Const::Fast and according to the documentation for Const::Fast it is an alternative to Readonly - this would however require another blog post.

All code examples used in this post are available on GitHub

Top comments (2)

Collapse
 
willt profile image
William Taylor

Nice post. Here is Const::Fast

 # cat const_fast_optimization.pl 
#!/usr/bin/env perl

use strict;
use warnings;
use v5.10;
use Const::Fast;

const my $DEBUG => 1;

if ($DEBUG) {
    print STDERR "Hello Happy World of Debugging\n";
}

exit 0;
# perl -MO=Terse const_fast_optimization.pl 
LISTOP (0x9313d0) leave [1] 
    OP (0x79eb20) enter 
    COP (0x931070) nextstate 
    UNOP (0x931530) entersub [3] 
        UNOP (0x923d90) null [148] 
            OP (0x8baba0) pushmark 
            UNOP (0x931390) srefgen 
                UNOP (0x931440) null [148] 
                    OP (0x8bab00) padsv [2] 
            SVOP (0x8ba9e0) const [7] IV (0x921cb0) 1 
            UNOP (0x99fb80) null [17] 
                PADOP (0x92b6d0) gv  GV (0x92fbe8) *const 
    COP (0x923c60) nextstate 
    UNOP (0x931120) null 
        LOGOP (0x931290) and 
            OP (0x930f60) padsv [2] 
            LISTOP (0x931310) scope 
                OP (0x923b50) null [183] 
                LISTOP (0x931180) print 
                    OP (0x9314b0) pushmark 
                    UNOP (0x9311e0) rv2gv 
                        PADOP (0x9312d0) gv  GV (0x7a4768) *STDERR 
                    SVOP (0x931220) const [8] PV (0x921c80) "Hello Happy World of Debugging\n" 
    COP (0x923d00) nextstate 
    UNOP (0x930fc0) exit 
        SVOP (0x931000) const [9] IV (0x921bf0) 0 
const_fast_optimization.pl syntax OK
Collapse
 
jonasbn profile image
Jonas Brømsø

Thanks for the example :-)

Any conclusions on your behalf?