Subroutine Signatures

Ovid on 2004-12-04T19:38:26

I finally got off my rear and started serious work on subroutine signatures. This now works:

use Sub::Signatures;

sub foo($bar) {
  print "$bar\n";
}

sub foo($bar, $baz) {
  print "$bar, $baz\n";
}

foo(1);     # prints 1
foo(2,3);   # prints 2, 3
foo(2,3,4); # fatal error

I capture the information necessary to do dispatch based upon typing, but the dispatch is currently only based on the number of arguments. Note that this works for subroutines and methods. Naturally, I have a TODO list:

  • Ignore prototyped subs
  • Allow typing
  • Can we make the typing optional?
  • How do we handle variadic subs? (I have ideas here)
  • Better error messages


Cool!

scottj on 2004-12-05T00:30:30

I really like this idea. I haven't ever minded the standard subs in perl, but this enhanced functionality will save time AND improve readaility. Very nice.

Re:Cool!

Ovid on 2004-12-05T01:08:41

Thanks. I really hope it works as well as it appears to. I think I will have an alpha for the CPAN either tonight or tomorrow. I now have it set up to handle method dispatch "loosely" based on the number of args or a "string" mode where it dispatches on the number and type (ref) or args.

My tests for the latter:

use Test::More 'no_plan';
use Test::Exception;

my $CLASS;
BEGIN
{
    $ENV{DEBUG} = 1;
    chdir 't' if -d 't';
    unshift @INC => '../lib';
    $CLASS = 'Sub::Signatures';
    use_ok($CLASS, 'strict') or die;
}

sub foo(ARRAY $bar) {
    return sprintf "arrayref with %d elements" => scalar @$bar;
}

ok defined &foo,
    'We can have typed subs with one argument';
is foo([6,6,6]), "arrayref with 3 elements",
    '... and they should behave as expected';

throws_ok {foo(0)}
    qr/Could not find a sub matching your signature/,
    '... but it will die if the signature does not match';

sub foo(HASH $bar)
{ $bar->{this} = 1; $bar }

is_deeply foo({ that => 2}), {this => 1, that => 2},
    '... and we can even specify different types.';

sub bar($bar) {
    $bar;
}

ok defined &bar,
    'We do not have to specify the type if it is a scalar';
throws_ok {bar([qw/an array ref/])}
    qr/Could not find a sub matching your signature/,
    '... but we had better not pass a non-scalar to it';

sub match($bar, Regexp $foo) {
    return $bar =~ $foo;
}

ok match('this', qr/hi/),
    '... and we can overload the methods as much as we like';

Note the last sub in particular. In "strict" mode, there's a lot of type checking that just goes bye-bye because you never have to worry about whether or not you have the correct data type. Since this is Perl, "strong typing" is limited to the types that ref returns. Of course, that also means you can do this:

sub print_all_params (CGI $q) {
    print $q->params;
}

Currently, if you have a subclass that fails. I might work around this at some point but I'm not sure. That depends upon whether or not people use this. This module will never get beyond alpha unless I know it's being used and is solid.

My major concern right now is that my tests are "vanilla" Perl. I've not hit anything unexpected, but someone will. I've been making notes on the things that will cause problems and will (hopefully) either write good tests for them or document the heck out of them.

How reliable is it?

Aristotle on 2004-12-05T16:21:21

Please just tell me it isn't based on source filters… :-) Otherwise, it's not for me.

But if it is and you are actually willing to use a filter beast in production, take a look at reformed-perl, which mostly forestalls your effort (though it's mainly OO-centrically) and includes more doodads. I don't know how refined it really is, but collaboration or something is probably be better than independent developments.

Re:How reliable is it?

Ovid on 2004-12-05T17:31:23

I found myself writing enough about source filters that I made a new journal entry to deal with this topic.

As for reformed-perl, it looks interesting and solves some problems with Perl 5's OO, but it's doing far more than I had intended and I suspect people would be less likely to use my more modest approach if bundled with that code. Personally I'd just use something like Class::MethodMaker with Sub::Signatures and get most of the benefits. Of course, if they want to borrow my code, they can.

The final nail in the coffin would be their test suite, which I reproduce here in its entirety:

use Test::More tests => 1;

BEGIN { use_ok( 'reform' ); }

I'm not saying their code is bad, but I think the philosophy is different enough that keeping this work separate is warranted.