The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Symbol::Approx::Sub - Perl module for calling subroutines by approximate names!

SYNOPSIS

use Symbol::Approx::Sub;

sub a {
  # blah...
}

aa(); # executes a() if aa() doesn't exist.

use Symbol::Approx::Sub (xform => 'Text::Metaphone');
use Symbol::Approx::Sub (xform => undef,
                         match => 'String::Approx');
use Symbol::Approx::Sub (xform => 'Text::Soundex');
use Symbol::Approx::Sub (xform => \&my_transform);
use Symbol::Approx::Sub (xform => [\&my_transform, 'Text::Soundex']);
use Symbol::Approx::Sub (xform => \&my_transform,
                         match => \&my_matcher,
                         choose => \&my_chooser);

New suggest mode.

use Symbol::Approx::Sub (suggest => 1);

DESCRIPTION

This is _really_ stupid. This module allows you to call subroutines by _approximate_ names. Why you would ever want to do this is a complete mystery to me. It was written as an experiment to see how well I understood typeglobs and AUTOLOADing.

To use it, simply include the line:

use Symbol::Approx::Sub;

somewhere in your program. Then, each time you call a subroutine that doesn't exist in the current package, Perl will search for a subroutine with approximately the same name. The meaning of 'approximately the same' is configurable. The default is to find subroutines with the same Soundex value (as defined by Text::Soundex) as the missing subroutine. There are two other built-in matching styles using Text::Metaphone and String::Approx. To use either of these use:

use Symbol::Approx::Sub (xform => 'Text::Metaphone');

or

use Symbol::Approx::Sub (xform => undef,
                         match => 'String::Approx');

when using Symbol::Approx::Sub.

Configuring The Fuzzy Matching

There are three phases to the matching process. They are:

  • transform - a transform subroutine applies some kind of transformation to the subroutine names. For example the default transformer applies the Soundex algorithm to each of the subroutine names. Other obvious tranformations would be to remove all the underscores or to change the names to lower case.

    A transform subroutine should simply apply its transformation to each item in its parameter list and return the transformed list. For example, a transformer that removed underscores from its parameters would look like this:

    sub tranformer {
      map { s/_//g; $_ } @_;
    }

    Transform subroutines can be chained together.

  • match - a match subroutine takes a target string and a list of other strings. It matches each of the strings against the target and determines whether or not it 'matches' according to some criteria. For example, the default matcher simply checks to see if the strings are equal.

    A match subroutine is passed the target string as its first parameter, followed by the list of potential matches. For each string that matches, the matcher should return the index number from the input list. For example, the default matcher is implemented like this:

    sub matcher {
      my ($sub, @subs) = @_;
      my (@ret);
    
      foreach (0 .. $#subs) {
        push @ret, $_ if $sub eq $subs[$_];
      }
    
      @ret;
    }
  • choose - a chooser subroutine takes a list of matches and chooses exactly one item from the list. The default matcher chooses one item at random.

    A chooser subroutine is passed a list of matches and must simply return one index number from that list. For example, the default chooser is implemented like this:

    sub chooser {
      rand @_;
    }

You can override any of these behaviours by writing your own transformer, matcher or chooser. You can either define the subroutine in your own script or you can put the subroutine in a separate module which Symbol::Approx::Sub can then use as a plug-in. See below for more details on plug-ins.

To use your own function, simply pass a reference to the subroutine to the use Symbol::Approx::Sub line like this:

use Symbol::Approx::Sub(xform => \&my_transform,
                        match => \&my_matcher,
                        choose => \&my_chooser);

A plug-in is simply a module that lives in the Symbol::Approx::Sub namespace. For example, if you had a line of code like this:

use Symbol::Approx::Sub(xform => 'MyTransform');

then Symbol::Approx::Sub will try to load a module called Symbol::Approx::Sub::MyTransform and it will use a function from within that module called transform as the transform function. Similarly, the matcher function is called match and the chooser function is called choose.

The default transformer, matcher and chooser are available as plug-ins called Text::Soundex, String::Equal and Random.

Suggest mode

Version 3.1.0 introduces a 'suggest' mode. In this mode, instead of just choosing and running an alternative subroutine, your program will still die as it would without Symbol::Approx::Sub, but the error message you see will include the suggested alternative subroutine. As an example, take this code:

sub aa {
  print "Here's aa()";
}

a();

Obviously, if you run this without loading Symbol::Approx::Sub, you'll get an error message. That message will say "Cannot find subroutine main::a". With Symbol::Approx::Sub loaded in its default mode, the module will find aa() instead of a() and will silently run that subroutine instead.

And that's what makes Symbol::Approx::Sub nothing more than a clever party trick. It's really not at all useful to run a program when you're not really sure what subroutines will be called.

But running in 'suggest' mode changes that behaviour. Instead of just running aa() silently, the module will still die() (as in the non-Symbol::Approx::Sub behaviour) but the message will be a little more helpful, as it will include the name of the subroutine that has been selected as the most likely correction for your typo.

So, if you run this code:

use Symbol::Approx::Sub (suggest => 1);

sub aa {
  print "Here's aa()";
}

a();

Then your program will die with the error message "Cannot find subroutine main::a. Did you mean main::aa?".

I like to think that some eighteen years or so after it was first released, Symbol::Approx::Sub has added a feature that might actually be of some use.

Thanks to Alex Balhatchet for suggesting it.

Subroutines

import

Called when the module is used. This function installs our AUTOLOAD subroutine into the caller's symbol table.

CAVEAT

I can't stress too strongly that this will make your code completely unmaintainable and you really shouldn't use this module unless you're doing something very stupid.

ACKNOWLEDGEMENTS

This idea came to me whilst sitting in Mark-Jason Dominus' "Tricks of the Wizards" tutorial. In order to protect his reputation, I should probably point out that just as the idea was forming in my head, he clearly said that this kind of thing was a very bad idea.

Leon Brocard is clearly as mad as me as he pointed out some important bugs and helped massively with the 'fuzzy-configurability'.

Matt Freake helped by pointing out that Perl generally does what you mean, not what you think it should do.

Robin Houston spotted some nasty problems and (more importantly) supplied patches.

AUTHOR

Dave Cross <dave@dave.org.uk>

With lots of help from Leon Brocard <leon@astray.com>

LICENSE

Copyright (C) 2000-2008, Magnum Solutions Ltd. All Rights Reserved.

This script is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

SEE ALSO

perl(1).