Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weakening captures using Sub::Quote

Tags:

perl

moo

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.

like image 637
Diab Jerius Avatar asked Nov 25 '16 19:11

Diab Jerius


1 Answers

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.
like image 71
ikegami Avatar answered Sep 18 '22 23:09

ikegami