Monkey Patch

TL;DR

Looking at the internals of Mojolicious is always interesting.

I was taking a look through the Mojolicious distribution, and in particular to the Mojo toolset, when I hit function monkey_patch:

 1 sub monkey_patch {
 2   my ($class, %patch) = @_;
 3   no strict 'refs';
 4   no warnings 'redefine';
 5   *{"${class}::$_"} = set_subname("${class}::$_", $patch{$_}) for keys %patch;
 6 }

Wikipedia defines Monkey Patch as follows:

A monkey patch (also known as “duck punching”) is a way for a program to extend or modify supporting system software locally (affecting only the running instance of the program).

Think to a class that does not have a specific method, but it would be soooo useful that you eventually add it.

The function is not complicated per-se, because it literally just installs functions with a specific name in the target package, but the idea to get stuff around and re-assemble them into another place is just amazing.

The monkey_patch function receives the name of the target $class to be extended, as well as key/sub pairs to install in it (line 2).

Line 5 fiddles with the internals of the package $class, which is usually a moderately well guarded place. For this reason, line 3 takes care to tell strict that it’s OK to fiddle with refs (i.e. we tell it we are supposed to know what we are doing) and we also ask warnings to close an eye if a function gets re-defined in the $class package (line 4). To some extent, these two lines represent a warning sign that some magic will happen but it’s intentional and for greater good.

Line 5 is the actual installation of the functions. It loops through the keys provided in input, and at each iteration it installs a sub (that is $patch{$_}).

The fully qualified name of the function is what we will be able to call this function by telling the interpreter where to find the function exactly, i.e. $class in our case. As such, it is just the name of the class, two colons ::, and the bare name provided in input, that is the following string:

"${class}::$_"

This name is used in two places within line 5:

  • in the left hand side of the assignment, to ask the Perl interpreter for the proper slot that we want to fiddle with (this is done using *{...}). This is where lines 3 and 4 came handy;
  • in the right hand side, to associate a name to the possibly anonymous sub that we are setting, via set_subname.

So… what’s this set_subname?!? Why use it instead of just installing the sub in the symbol table? Because we want to have clarity in stack traces!

Let’s take a look at an example anonymous sub:

$ perl -MCarp=confess -e 'my $x = sub { confess "inside a sub" }; $x->()'
inside a sub at -e line 1.
	main::__ANON__() called at -e line 1

The Perl interpreter does not know how this anonymous sub is called, so it calls it… __ANON__. This can be admittedly ugly and somehow obscure if you’re doing some troubleshooting.

Now… enter set_subname from Sub::Util:

$ perl -MCarp=confess -MSub::Util=set_subname -e '
    my $x = sub { confess "inside a sub" };
    set_subname(my_hazardous_sub => $x)->();'
inside a sub at -e line 2.
	main::my_hazardous_sub() called at -e line 3

This is much better, isn’t it?!?

So… this little monkey_patch can be very handy when used with care, and as an added bonus I discovered about set_subname 🤩


Comments? Octodon, , GitHub, Reddit, or drop me a line!