Redesigning Package::Strictures

Package::Strictures on metacpan

Design goals

  1. An infrastructure for having “optional” code blocks that can be eliminated prior to execution.
  2. Ideally, these code blocks can be toggled by calling code and then eliminated from the execution tree at the OP level.
  3. In essence, delegate 100% of the “Speed vs Correctness” concern to the consumer of the code, not the implementer.

Presently implemented using dead-code folding mechanisms.

a();
if( 0 ){ 
   /* code */ 
}
b()

Which becomes

a();
/* noop */
b();

Additionally, this is implemented with perl constant subs so that the if logic is performed at code body compile time, eliminating any run-time penalties from doing the check:

BEGIN {
   if( $ENV{A} ) { 
           eval "sub foo(){ 1 }";
   } else {
           eval "sub foo(){ }";
   }
}

sub runs_billions_of_times {
  a();
  if( foo ){
    c();
  }
  b();
}

So that at run time, the sub will either be

sub runs_billions_of_times {
  a();
  c();
  b();
}

or

sub runs_billions_of_times {
  a();
  b();
}

( With the goal being there is no runtime if test done in each and every loop ).

The problem here is of course its incredibly order dependent, and the method of action works as such:

  1. Consuming modules declare the style of behaviour they want from a module ( ie: fast or strict ) in advance to the Package::Strictures interface.
  2. Modules are then loaded.
  3. During module compile time, any predeclared information is used to define the value of the constant subs.
  4. Code bodies are then compiled and dead-code elimination is performed.
  5. Further behaviour requests of the module are frozen due to code compilation.

This largely “Works” at present, just it feels somewhat a dangerous way to implement it, and the syntax is a bit … well, wonky. I tried to take inspiration from the warnings::register interface, but it leaves much to be desired.

“Traditional” approaches

Usually, people who want a behaviour like this, opt for one which doesn’t give them compile-time code elimination.

package Foo::Bar;

our $STRICT;

sub foo { 
   ...   
   if( $STRICT ){ 
       ...
   }
   ...
}

This of course solves 90% of the problem, however, often the reason behind not wanting the strict behaviour to start off with is performance concerns.

Sure, this may be premature optimisation.

But if you do have code that is intended to run very many times in a tight loop, you want as “Bare metal” performance as possible, and even the simple if ( $cond ) { encapsulation can add undesirable performance negatives in that case. To eliminate the if, people may be tempted to do rather nasty things, like:


sub foo_fast { 
   ...
}
sub foo_strict { 
   ...
   # strictures code here.
}

*foo = \&foo_fast;

sub setmode {
    my ( $self, $mode ) = @_ ; 
    ...
     *{__PACKAGE__ . "::foo"} = \&{__PACKAGE__ . "::foo_" . $mode };
}

And this is bad, bad because you’re then having to maintain 2 separate implementations of the same code, which could easily get out of sync and get wildly different behaviour with the “strict” mode version.

Another approach people might be tempted to use: Templating.

sub _gen_foo { 
     my ( $mode ) = @_ ;
     my ( $code ) = <<'EOF';
  sub {
    # prelude
    [IF STRICT]
       # strict code 
    [ENDIF]  
    #postlude 
  };
EOF;
   return eval template_compile($code, { STRICT => ( $mode =~ /STRICT/i ) });
}
sub _gensub {
    my ( $sub, $mode ) = @_ ; 
    my $method = __PACKAGE__->can('_gen_' . $sub );
    *{__PACKAGE__ . '::' . $sub } = $method->( $mode );
}

for my $i (qw( foo )){ _gensub( $i, 'fast' ); }

And this is bad for a plethora of reasons. You lose a huge amount of compile-time checking here, and you won’t know if there are even syntax bugs in the “Strict” code until you run in strict mode, and if strict mode gets “broken”, it could silently go unnoticed till somebody tried to use it.

There’s also the “Breaks the syntax highlighter” argument, though, that of course depends on the highlighter. Github’s markdown embedded highlighter seems to be “ok” with it because it seems not to mark up contents of HEREDOCS ( <<"EOF" ) as strings, and just parses them bare.

And having seen my share of “Code stored in strings” in various projects, using too much of that is one of those things that will make your developer want to kill either themselves or you. Storing large amounts of code in string templates → bad idea.

What would be nice

I would like for the “Compile time or GTFO” problem to not be so prevalent, and I’d like the load order to not be so important.

To this end, it would be nice to have a way of marking code locations in the OP tree, and being able to post-compilation remove/re-insert code blocks, ie:

package Foo::Bar;

use Package::Strictures 
     -register => [qw( STRICT )],
     -defaults => [];
  
sub foo {
   my ( $self, @args ) = @_;  
   STRICT {
      if ( not ref $self ) { 
         # error due to not being called as a method etc 
      }
      if ( some other thing here ) { 
         return somevalue; #  ie: the STRICT value should behave like a label for the enclosed code, so returns from that code returns from the enclosing context )
      }
   }
}

And then later:

use Foo::Bar; # loaded, but strictures are off!

... 
use Package::Strictures -for => { 'Foo::Bar' => [qw( STRICT )] };
... 
# or perhaps
Foo::Bar->strictures(qw( STRICT ));
.... 
# either way 


for ( 0 .. 1000 ) { 
      Foo::Bar->foo(); # as per default, runs fast without strict checks.
}
# ~ some stanza that enables strict checks
for ( 0 .. 1000 ) { 
      Foo::Bar->foo(); # runs slow with strict checks.
}
# ~ some stanza that disables strict checks
for ( 0 .. 1000 ) { 
      Foo::Bar->foo(); # runs fast without strict checks.
}

A more advanced idea that might be nice is being able to lexically enable strictures “somehow”, but that is conceivably complicated to implement, and will probably introduce runtime penalties each time the sub is called trying to determine which code should be called.

Leave a comment

About KENTNL

user-pic Surprise!