Method::Signatures: where and when

I’ve just released a new version of Method::Signatures: 20121201, a.k.a. yesterday.  This one has been a long time coming—I thought for a while there that $work was going to either bury me or sap my will to live—but it’s finally here, and I hope you MS fans out there will be rejoicing.  Let’s take a look at some of the goodies.

(By the way, most of these awesome new features came from the Damian, as I described in my post about Git and patches.  The remainder come from schwern, who is of course the original author of MS.  This time around, I really am just the guy who put it all together and uploaded it for all y’all.)

where Constraints

One thing that MooseX::Method::Signatures has always been able to do that MS (and consequently Method::Signatures::Modifiers) couldn’t was what are called where constraints.  This is a Moose concept that’s a bit like on-the-fly typing.  For instance, if you declare that your parameter is an Int, what you’re actually saying is, “please check the incoming value of this parameter and, if it’s not an integer, please throw a fit about it, thankyouverymuch.” If you wanted to make sure that you were getting an integer less than 10, say, what you could do is define a new type, say IntUnderTen.  But that might be a huge pain in the butt, particularly if you had a lot of different maximum values, or if said maximum was parameterized.  So an easier way to do it would be:

method foo (Int $bar where { $_ < 10 }) { do_stuff_with($bar) }
And this is exactly the syntax that MXMS uses (and is similar to, if not the same as, the Perl 6 syntax).  We’ve long wanted to add this syntax to MS as well; we just never had the tuits.  Happily, the Damian has done it for us, and now it’s available for all your constrainting needs.

Conditional Defaults

A while back, Ruz sent us a patch concerning default values.  Your standard default value goes like so:

method foo ($bar = 'fred') { say $bar }
So if you call it like so:
$obj->foo('sue');  # you get "sue"
but if you call it like so:
$obj->foo();  # you get "fred"
But what happens if you call it like so?
$obj->foo(undef);
You get a warning about uninitialized values, that’s what.  See, defaults only apply if you explicitly omit the parameter.  If it’s present, but undefined, the default isn’t even consulted.  But suppose you want the default when it’s undefined?  Well, you could always:
method foo ($bar = 'fred') { $bar //= 'fred'; say $bar }
But that’s a bit redundant, right?  Well, what Ruz suggested was something more like this:
method foo ($bar //= 'fred') { say $bar }
Now, the default is still applied if the parameter is omitted altogether, but it’s also applied if the value is undefined.  Ruz also pointed out that you might want to apply the default if the value is false in any way:
method foo ($bar ||= 'fred') { say $bar }
Or, say: what if you’re reading off a web form?  You might get ‘0’ back, in which case you don’t want the default, but if you get ’’ back, it probably just means the user left it blank, so proably you do want the default.  Maybe something like:
method foo ($bar ''= 'fred') { say $bar }
The concensus on this patch (from everyone except Ruz) was that //= was a great idea, ||= was okay, but ''= was just too weird.  But schwern and Damian came up with a way to satisfy everyone:
method foo ($bar = 'fred' when undef) { say $bar }  # if it's not defined
method foo ($bar = 'fred' when 0) { say $bar }  # if it's explicitly zero
method foo ($bar = 'fred' when '') { say $bar }  # if it's the empty string
method foo ($bar = 'fred' when { !$_ }) { say $bar }  # if it's false
method foo ($bar = 'fred' when { !/\S/ }) { say $bar }  # if it's all spaces
Of course the defaults are still applied if the argument is omitted—it’s always applied if it’s omitted—but they can also be applied in just about any other situation too.  The actual test is done using the smart match operator, so you can do any wacky thing you like, like testing against an array or regexen or whatnot.  This is another great feature from Damian, and he also supplied a shortcut for the most common one:
method foo ($bar //= 'fred') { say $bar }  # same as: = 'fred' when undef
Which is where we started.

No More Devel::BeginLift

We had long used Devel::BeginLift to make a MS method or function parse at compile-time, just like a real Perl sub.  But it turns out this is a problem for our Windows brethren and sistren, because Devel::BeginLift doesn’t want to compile on Windows.  Happily, schwern found another way to achieve the compile-time goodness and we’ve dropped DBL as a prerequisite.  You should be able to MS to your heart’s content on Windows now.

Disabling Parameter Checking

Normally, if someone passes more arguments to you than you were expecting, that’s an error.  But occasionally you find yourself in the position of needing to create a function or method that’s being called from someone else’s code, and perhaps they don’t play nice, or they are sending arguments that other people need but you don’t.  In these sorts of cases, you could always just slap a slurpy parameter in there:

method foo ($bar, $baz, @stuff_I_dont_care_about) { do_stuff($bar, $baz) }
But one always hates to pay for an array copy you’re never going to use.  Or create a variable you’re never going to access.  Would just be nicer if you could just say to MS “you know what? at this point, just stop caring about the parameters, okay?” Well, now you can:
method foo ($bar, $baz, ...) { do_stuff($bar, $baz) }
Or maybe you don’t need any arguments at all:
method foo (...) { say ':-P'; }
Another useful feature from Damian.

Signature Parsing Improvements

Three final miscellaneous improvements: the first two from Damian, the final one from schwern.

This has always worked:

method foo (Str|ArrayRef $bar) { handle_multi_types($bar); }
But this didn’t used to:
method foo (Str|ArrayRef|HashRef $bar) { handle_multi_types($bar); }
Now it does.


Likewise, this always worked:

method foo (ArrayRef[Str] $bar) { process($bar); }
But this didn’t used to:
method foo (ArrayRef[ArrayRef[Str]] $bar) { process($bar); }
Now it does.


Finally, this didn’t used to work:

method foo ($bar, $baz, ) { do_stuff($bar, $baz); }
Now it does.  (The trailing comma is just ignored, exactly as it would be in a list definition.)

Results from CPAN Testers

I haven’t seen any reports for this new version yet, but the last dev version that I saw reports for had a failure rate under 10%.  Most of those failures I was able to identify and fix (just little buglets in the tests themselves).  But there’s still a few reports of failures on Windows (about a third of the total Windows reports, last time I checked) that I can’t quite pin down.  They don’t seem consistent based on Perl version, Windows version, or anything else I could find.  If anyone tries out MS on Windows and sees a failure that looks like this, please open up a GitHub issue and let me know.

Competition

Thanks to Perl Weekly (which is absolutely great for discovering such things; you really should toddle off and sign up if you haven’t already), I see that MS has some new competition: Function::Parameters, by Mauke.  It looks like FP has roughly the same syntax as MS; in fact, it looks like Mauke has used some of MS’s test suite in FP, which will help him guarantee that he maintains compatibility (a pretty good thought on his part).  Of course, MS does a few things that FP doesn’t do (the biggest one being types, but also the new features above and a few other bits and bobs, such as explicit optional/required, alias parameters, readonly parameters, etc).  But, then, FP does a few things that MS doesn’t do:

  • You can add a prototype to an FP function.
  • You can change the keyword used to introduce a function or method with signatures.
  • You can turn checking for required parameters and unexpected parameters off.
(You might think that handling function attributes is something else FP does that MS doesn’t, but MS actually does handle those.  We just don’t advertise it much.) I wonder if anybody thinks of any of these as big holes in MS’s feature set.  Please let me know in the comments if you do.


Of course, the big difference is that MS uses Devel::Declare, whereas FP uses the pluggable keywords API introduced in 5.12.  I think that’s pretty much an invisible, under-the-hood difference, but perhaps it will matter to some.  In any event, competition is always good, so I welcome the new face on the function signatures block.

Future Plans

What I’ll be working on next is a small-ish patch from Damian which will allow you to have named alias parameters (right now, you can have named parameters, and you can have alias parameters, but you can’t have named alias parameters).  There are also a few minor GitHub issues I want to take a look at.  Nothing major.

I’m also very interested in hearing from folks that are using MS.  Have a problem? or a suggestion?  Don’t hesitate to open a GitHub issue, even if it’s just to chat about something.  I love hearing from people who are actually using this in the real world.

So install the latest version, give it a whirl, and let us know how it goes for you.  Happy signature parsing!

9 Comments

When "MS" and "Windows" are in the same sentence, "MS" has usually an other expansion...

Congratulations Buddy on this release. Great to see Method::Signatures working on Windows. Thank you!

Mauke has an actively developed branch of Function::Parameters with support for Moose types. Not on CPAN yet though.

Thank you for your efforts! Method::Signatures is a key part of my Moose toolbox. Very much appreciated.

Hi Buddy, thanks a lot for Method::Signatures.

One naive (unrealistic?) idea/request:

Could there be any way for a user to tell in a Moo(se) class: “don’t do any type check for any subroutines of this class” ?

Maybe in the same way that Debuggit skips useless debuggit() function calls ? (“Performance Considerations” in Debuggit::Manual - implemented with B::Deparse)

We could then develop/debug with type checks on, and not use type checking in production.

I should probably have written "in a package", rather than "in a Moo class".

Leave a comment

About Buddy Burden

user-pic 5 years in California, 15 years in Perl, 25 years in computers, 45 years in bare feet.