SMOP Capture Expansion


Basics

Capture expansion means taking different objects and using them as part of the actual capture of a call. That means that the number of positional arguments, as well as the set of named arguments might not be determined at compile time.

The syntax for that is:

  bar(|$capture_object, |@positional_arguments, |%named_arguments)

The prefix:<|> operator implies "Capture" context, which in SMOP means a .Capture() call. This is needed so that

my $a = (1,2,3);
foo(|$a);

works as expected. When you do

my $a = \(1,2,3);
foo(|$a);

It creates a capture object at the first line, and the capture object then return itself in the "capture context".

Mixing several captures

When mixing different capture objects, the positional argument order must be preserved and named arguments conflict should raise warnings. Additionally, the mixing of inline arguments with capture expansion should produce other capture objects that will then be mixed together...

 bar($a, |$b, $c, |@d, :foo<bar>, $e, |%f)

In that case, in order to preserve positional order, as well as preserve the priority on named arguments definition (the last overwrite the first, raising a warning), we need to actually create 6 different capture objects and mix them in the correct order:

 my $c1 = \($a);
 my $c2 = \($c);
 my $c3 = \(:foo<bar>, $e);
 bar(|$c1, |$b, |$c2, |@d, |$c3, |%f)

Capture Merger Object

In order to avoid making copies of all the arguments in all the inner captures, the "Capture Merger" object will hold references to all the inner capture objects and proxy to that information, making sure the context propagation happens as late as possible.

Capture Merger API

class CaptureMerger {
 has @.captures;

 method elems {
   [+] @.captures.elems;
 }

 method postcircumfix:<[ ]> ($index) {
   -> $merger, $capture, $offset {
     fail('Non-existant positional argument') if $capture >= $merger.captures.elems;
     $merger.captures[$capture].elems > $offset ??
       $merger.captures[$capture][$offset] !!
         &?BLOCK($merger, $capture + 1, $offset - $merger.captures[$capture].elems);
   }(self, 0, $index);
 }

 method postcircumfix:<{ }> ($key) {
   -> $merger, $capture {
     fail('Non-existant named argument') if $capture < 0;
     $merger.captures[$capture].:exists($key) ??
       $merger.captures[$capture]{$key} !!
         &?BLOCK($merger, $capture - 1);
   }(self, $.captures.elems - 1);
 }

}