Implementing Did You Mean in Perl
A couple of weeks ago Yuki Nishijima released a clever Ruby gem called “Did You Mean”, that intercepts failed method calls and suggests the closest matching (correct) method in the exception message. I wanted to create an equivalent module in Perl, and so armed with a limited appreciation of AUTOLOAD
I set about creating Devel::DidYouMean.
Using the module
Devel::DidYouMean is available on CPAN now, you can install it at the command line:
$ cpan Devel::DidYouMean
To use the module, just import it with use
like any other module:
# script.pl
use Devel::DidYouMean;
print substring('have a nice day', 0, 6);
This code calls a builtin function “substring”, which does not exist. Running the above code we get a more helpful error message:
Undefined subroutine 'substring' not found in main. Did you mean substr? at script.pl line 4.
How it works
As I alluded to in the introduction, DidYouMean.pm defines a subroutine using the AUTOLOAD
function which catches missed subroutine calls. But by default this subroutine only exists within the Devel::DidYouMean namespace so it would only fire when there was a missed method call like Devel::DidYouMean->some_method;
. This is not very useful! So I used some symbol-table black magic to load the module into every namespace at runtime:
CHECK {
# add autoload to main
*{ main::AUTOLOAD } = Devel::DidYouMean::AUTOLOAD;
# add to every other module in memory
for (keys %INC)
{
my $module = $_;
$module =~ s/\//::/g;
$module = substr($module, 0, -3);
$module .= '::AUTOLOAD';
# skip if the package already has an autoload
next if defined *{ $module };
*{ $module } = Devel::DidYouMean::AUTOLOAD;
}
}
Walking through this code, you might be wondering what that strange CHECK
block is for. This ensures that the code within the block is loaded after the program compilation phase has finished, reducing the risk of the program loading another module after DidYouMean.pm has already exported it’s AUTOLOAD
subroutine. Perl defines several named code blocks (you are probably familiar with BEGIN
). The downside of using a check block is if the module is loaded using require
instead of use
, this block will not be executed at all.
The code then adds the AUTOLOAD
subroutine to main (the namespace of the executing program) and every other namespace in the symbol table. I got the syntax for this from the “Dynamic Subroutines” chapter of Mastering Perl.
The code for the autoloaded subroutineis long, so I won’t reproduce it here. High level, it extracts the name of the failed subroutine called from $AUTOLOAD
and using the Text::Levenshtein module, calculates the Levenshtein distance between the name of the failed subroutine call and every available subroutine in the calling namespace. It then croaks displaying the usual undefined subroutine error message with a list of matching subroutines.
Conclusion
Although the module “works”, it feels heavy-handed to export a subroutine to every namespace in memory. An alternative approach that I considered but couldn’t get to work would be to define the code in an END
block, and then check if the program is ending with an “unknown subroutine” error. This challenge with this is that in the end phase, Perl has already nullified the error variable $!
so it’s hard to know why the program is ending (tieing $!
might get around this). If you’re interested in tackling this challenge, the repo is hosted on GitHub, pull requests are welcome :) The module documentation has more examples of Devel::DidYouMean in action.
Update:Devel::DidYouMean now uses a signal handling approach and avoids AUTOLOAD altogether 2014-11-09
This article was originally posted on PerlTricks.com.
Tags
David Farrell
David is a professional programmer who regularly tweets and blogs about code and the art of programming.
Browse their articles
Feedback
Something wrong with this article? Help us out by opening an issue or pull request on GitHub