wrap and unwrap

JonathanWorthington on 2009-06-08T22:39:50

A while back, I did a Rakudo day and didn't get around to writing a report (probably because I hacked until the point where all I wanted to do was sleep). The main thing I did on that day was implement .wrap and .unwrap, which I'm going to talk about in this post.

The .wrap method on a routine allows you to modify it in place, giving it some extra behaviors before (optionally) delegating or calling the original. This allows for pre-processing of arguments, post-processing of return values, logging and so forth.

Here is a simple example.

sub do_some_work() { say "doing stuff" }

do_some_work();
say "--";

my $handle = &do_some_work.wrap(sub () {
    say "About to do some work";
    callsame();
    say "Finished doing some work";
});
do_some_work();
say "--";

&do_some_work.unwrap($handle);
do_some_work();

So, first we take a sub and call it. The result is just as you would expect. Then we wrap it with another sub - an anonymous one. We could have used a closure or pointy block here too. This some uses callsame() to call the original sub. If we had any arguments passed in, it would have passed the same arguments along. You can use "callwith" instead to supply a different set of arguments, if you had done some pre-processing on the arguments and wanted to pass along the modifications. Calling .wrap gives us back a handle. If we want to remove the wrapper that we installed, we simply call unwrap on the routine, passing the handle, and the wrapper is removed. So the output of this program is:

doing stuff
--
About to do some work
doing stuff
Finished doing some work
--
doing stuff

Let's take a look at a second example.

sub foo() { say 1 }

foo();
say "--";

my $h1 = &foo.wrap({ say 2; nextsame; say "not here"; });
foo();
say "--";

my $h2 = &foo.wrap({ say 3; nextsame; say "nor here"; });
foo();
say "--";

&foo.unwrap($h1);
foo();
say "--";

&foo.unwrap($h2);
foo();

This time, we do a couple of things differently. We use the nextsame function instead of callsame. nextsame defers rather than calls, so after calling nextsame we are never inside the current routine again. Thus the "not here"/"nor here" say statements will never be executed. Secondly, we unwrap in a different order than we wrapped. This demonstrates the use of out-of-order unwrapping, meaning you can add and remove behaviors as you wish without having to worry where in the list of wrappers they are. The output of this program is:

1
--
2
1
--
3
2
1
--
3
1
--
1

We also have tests passing where we wrap and unwrap in loops, applying the same closure as a wrapper many times, which also works.

Since doing the initial cut of wrapping on a Rakudo Day, I have further improved and refactored .wrap and .unwrap as a part of the changes in my current Hague Grant. While there's some more edge cases that need checking out and testing, we now pass just about all of the tests for wrapping (those we don't are related to the interaction between wrap and temp, and we don't have temp yet in Rakudo). So, looking pretty good.

Thanks to Vienna.pm for sponsoring the Rakudo Day that saw us get wrap support.


method modifiers?

j1n3lo on 2009-06-09T09:16:24

Am I right to assume that this is how Moose method modifiers are represented in Perl6?

Re:method modifiers?

JonathanWorthington on 2009-06-09T12:11:19

They look to be covering very much the same problem space, yes. However, I don't see anything in the Moose docs you linked on unwrapping.

Re:method modifiers?

j1n3lo on 2009-06-09T12:35:46

Cool. Keep up the good work on Rakudo! Its coming on nicely indeed ;-)

Reserved words

mir on 2009-06-09T09:39:38

Does this mean that callsame and callwith are reserved words? And wrap and unwrap reserved methods?

Re:Reserved words

JonathanWorthington on 2009-06-09T12:17:13

Well, there's no such thing as a "reserved method" really. These are just methods defined on the (built-in) Routine class. Sub and Method and Submethod just derive from this class and thus you get .wrap and .unwrap available on them.

callsame, callwith, nextsame and nextwith are all built-in subs. They aren't recognized by the compiler specially, they just dispatch like any old subroutine. (Under the hood, they are implemented in terms of a candidate list plus scope and lexpad introspection. This same candidate list mechanism is what gives you deference or calling in a method to the next parent. That's one of the things I'm currently hacking on for my Hague Grant.)

Hope this helps,

Jonathan