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 AUTOLOADsubroutine 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

Tags

David Farrell

David is the founder and editor of PerlTricks.com. An organizer of the New York Perl Meetup, he works as a technology consultant in New York City.

Browse their articles