Diab Jerius Diab Jerius - 14 days ago 7
Perl Question

Weakening captures using Sub::Quote

I'd like to weaken captured variables in the code generated by Sub::Quote. For example, here's the non-quoted alternative:

use 5.10.0;
use Scalar::Util qw[ weaken ];
{
my $s = 'foo';
my $x = sub { say $s };
weaken( my $y = $x );

my $bar = sub { &$y };
&$bar;
$x = undef;
&$bar
}


and the output:

foo
Can't use an undefined value as a subroutine reference [...]


And here's my Sub::Quote attempt:

use 5.10.0;
use Sub::Quote;
use Scalar::Util qw[ weaken ];
{
my $s = 'foo';
my $x = sub { say $s };
weaken( my $y = $x );

my $bar = quote_sub( '&$y', { '$y' => \$y } );
&$bar;
$x = undef;
&$bar;
}


and the output:

foo
foo


Obviously the captured
$y
isn't weakened. Is there a way of altering the generated code to weaken captured variables?

The documentation is sparse, and the
Sub::Quote
implementation is complex; I'm fairly convinced this isn't possible with the current code, but I'd love to be shown to be wrong.

Answer
my $bar = quote_sub( '&$y', { '$y' => \$y } );

is roughly the same as

my $bar = eval(q{ my $y = $y; sub { &$y } });

(It does more, but those bits are irrelevant to this question). As you can see, that creates a new strong reference to the sub[1].

As a workaround, you could add a layer of indirection:

my $bar = eval(q{ my $y_ref = \$y; sub { &{ $$y_ref } } });

This can be achieved by using:

my $bar = quote_sub( '&{$$y_ref}', { '$y_ref' => \\$y } );

There wouldn't be any problems if the $y created by Sub::Quote was an alias for your $y. This can be achieved using Data::Alias or an experimental feature introduced in 5.22.

This can be demonstrated using the following:

{
  package Sub::Quote;

  my $sub = sub {
    my ($from, $captures, $indent) = @_;
    join(
      '',
      "use feature qw( refaliasing );\n",
      "no warnings qw( experimental::refaliasing );\n",
      map {
        /^([\@\%\$])/
          or croak "capture key should start with \@, \% or \$: $_";
        (' ' x $indent).qq{\\my ${_} = \\${1}{${from}->{${\quotify $_}}};\n};
      } keys %$captures
    )
  };

  no warnings qw( redefine );
  *capture_unroll = $sub;
}


my $bar = quote_sub( '&$y', { '$y' => \$y } );

You could talk to the module's maintainer about adding an option that would cause the use of aliasing.


  1. When you create a copy of a (strong or weak) reference, it's a strong reference.