develooper Front page | perl.perl5.porters | Postings from June 2020

Dual-life perl 5-or-7 code and prototypes - impossible?

Thread Next
Paul "LeoNerd" Evans
June 27, 2020 11:45
Dual-life perl 5-or-7 code and prototypes - impossible?
Message ID:
TL;DR: I believe it impossible to write a dual-life module using
  function prototypes that works on both perl 5 and perl 7.

I have been experimenting with what it might be like to maintain a dual
perl5-and-7 module that uses prototypes on functions. Core's List::Util
would be a good candidate except it's all written in XS, so never mind.
Next on my list is List::UtilsBy.

Right now it has lots of code looking like

  sub sort_by(&@) { ... }

So, first off I trivially just rewrite all of those into

  sub sort_by :prototype(&@) { ... }

And hey presto, my module works fine on 5.20 onwards, and presumably 7.

Now all I have to do is handle the pre-5.20s, right? ;)


  $ perl5.18.2 lib/List/ 
  Invalid CODE attribute: prototype(&@) at lib/List/ line 147.

So my first thought was to inject a MODIFY_CODE_ATTRIBUTES.

Simply put, this doesn't work. A naive attempt which invokes
Sub::Util::set_prototype silently has no side-effect - it doesn't warn
or fail, it just doesn't set the prototype. I haven't investigated why
yet, but I have a bug open:

I think it's probably because MODIFY_CODE_ATTRIBUTES runs too early in 
the parser. I have to defer it until UNITCHECK time. See more code

While that works, it produces lots of warnings:

  CODE package attribute may clash with future reserved word: prototype at lib/List/ line 147.

OK, well I can sortof fix that one too, by injecting a
`no warnings 'reserved'".

So the current code in List/ looks like:

   if( $] < 5.020 ) {
      require Sub::Util; Sub::Util->VERSION( '1.40' );
      warnings->unimport( qw( reserved ) );

      my @prototypes;

         my ( $pkg, $code, @attrs ) = @_;

         my @ret;
         foreach my $attr ( @attrs ) {
            if( $attr =~ m/^prototype\((.*)\)$/ ) {
               my $prototype = "$1";
               push @prototypes, [ $code, $prototype ];
            push @ret, $attr;

         return @ret;

         foreach ( @prototypes ) {
            my ( $code, $prototype ) = @$_;
            Sub::Util::set_prototype( $_->[1], $_->[0] );

And note that there are two large problems still unaddressed here:

 1) This code has to suppress all "reserved" warnings, not just those
    relating to the :prototype attribute

 2) This code has to appear in List/ directly because of that
    UNITCHECK phaser. It would not be possible to inject this entire
    thing via a module.

Point 1 is a slight annoyance, but point 2 really is the killer. If
we're already worried about people having to copy lines of boilerplate
about "use strict; use warnings;" around the place I'd hate to have to
copy the above ~30 lines of code between every one of my
prototypes-using modules.

I had originally hoped to move all this code into a simple module we
could `use`:

   use Sub::Attribute::Prototype;

Such a module would be a no-op on 5.20+, and inject something like the
above code on earlier perls. But the presence of that UNITCHECK phaser
appears to make this impossible. I haven't investigated the RT bug
linked above yet, but I suspect it won't be an easy fix because of the
order in which operations happen. Operations that are already fixed in
the design of older perl interpreters; remember this is a pre-5.20 only
problem. Even if I do find a fix for that issue, it will likely need a
new version of the XS module, which adds a little annoyance but not
impossible. That still leaves unfixed the `reserved` warnings though.

Someone - please prove me wrong. Please demonstrate a way to write a
prototype-using module that will work nicely on perls older than 5.20,
and also on 7. I allege it cannot be done.

Paul "LeoNerd" Evans      |  |

Thread Next Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at | Group listing | About