6

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.

ikegami
  • 367,544
  • 15
  • 269
  • 518
Diab Jerius
  • 2,310
  • 13
  • 18
  • Does the `weaken` there actually do anything? If it worked, I would've expected `$y` to become `undef` right after `weaken $y`. – melpomene Nov 25 '16 at 20:09
  • @melpomene, There is a second reference to the CV (as seen using Devel::Peek's `Dump`), though I don't know what it is. /// That said, that means that `$y = undef;` doesn't actually free the sub as the OP expects. The call to `weaken` could be removed without affecting the demo. – ikegami Nov 25 '16 at 21:39
  • @ikegami I'd assume the second reference is in the optree itself, because the sub is not a closure, so it's probably created during compilation and just kept around forever. – melpomene Nov 25 '16 at 22:11
  • @melpomene, Oh, *because it's not a closure*, it just returns a reference to the single permanent instance every time you execute `sub { say 'foo' }`. Putting that to the test – ikegami Nov 25 '16 at 22:25
  • @melpomene, Bingo. Updated the question and the answer. – ikegami Nov 25 '16 at 22:33
  • I apologize, the code I posted not only did not compile (due to an embarassing cut'n'paste error) but also illustrated confusion on my part about weaken. Thanks to @ikegami for the corrections. – Diab Jerius Nov 25 '16 at 23:55

1 Answers1

4
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.
ikegami
  • 367,544
  • 15
  • 269
  • 518