Signatures vs. Methods

I've still been thinking about proper OO in the Perl core and hit an interesting case.

Imagine the following, hypothetical Perl 5 OO syntax. Inheritance is handled via is and inheritance order is assumed to parent class declaration order. Thus, UnlovedChild inherits from MissingFather first.

What do you think the output should be?

class MissingFather {
    method shout() { say "I'm outta here!" }
}

class DrunkenMother {
    method shout($message) { say "$message!" }
}

class UnlovedChild is MissingFather, DrunkenMother {}

UnlovedChild->shout("Where's my beer?!")

I currently have this code mostly working locally and if you run it you get an error like:

`Too many arguments for subroutine 'MissingFather::shout' at ...

That's because method resolution in Perl is based on the name of the method (or, sadly, the subroutine) and we can easily select the wrong method. If we were to identify methods by name and arity (the number of arguments), we could say that MissingFather doesn't listen and thus has a shout/0 method while DrunkenMother just yells back whatever you say to her via shout/1. It's quite reasonable to expect that when UnlovedChild shouts, his DrunkenMother shouts back at him because we tried to call shout/1, not shout/0, but because we inherit from MissingFather first, we get shout/0 and a fatal error, instead of the behavior we were looking for.

So if we go with arity-based method resolution (and C3), we can at least try to call the method the developers expect. I'm not saying this is perfect, but we can try. Even in a linear tree (A -> B -> C), calling a method on C might just skip a similarly named method in B and go straight to A because A matches the arity. This system works reasonably well for other languages, such as Clojure and Prolog. Further, because it's arity-based and doesn't require complex resolution, it should be simple to implement.

But there are some issues with this.

First, should we do this? The UnlovedChild behavior might be arguably wrong because:

  1. Multiple inheritance is crap
  2. Even if it weren't, the child shouldn't really inherit from MissingFather and DrunkenMother as it might in the real world
  3. Having similar methods with different signatures might be viewed as a code smell (and starts taking us down the rabbit hole of multi-dispatch)

Second, if we use arity-based dispatch, what about forward declarations?

sub foo; # legal
sub foo($bar); # no signatures allowed on forward declarations

Third, how does role-based composition work with arity? If your class consumes a role with a foo/2 method and you implement a foo/1 method, is that a conflict? If it isn't, we've a hybrid of identifying methods by name or name+arity. If it is, we've gone down the road of arity-based multi-dispatch. I imagine this would likely entail very, very significant changes to the Perl core.

And if you think we should get into types, and putting those into signatures, you can read more about the complexity of finding the right method.

I would love to hear your thoughts.

4 Comments

One can think of arity as part of the name, in which case foo/1 will never fall back to foo/0.

Since this is Perl we're talking about with arity being fluid, a forward declaration to me seems to be "foo/*" - an unspecified arity matching any call. Which would also be the case for a method declared without a signature. You've pointed out a need for a forward declaration-with-sig syntax.

If you have any interest in interoperating with Perl code written before this new syntax then none of this arity-based dispatch is even on the table. How is this going to be reflected in the package stash? With an arity-mangled name? Is method call supposed to fall back to the unmangled name glob for old-style code? What is the timing of that fallback supposed to be? Or do you want to extend globs and somehow bung all of the arities into the same glob? (And then how does it work with coderefs?)

I’m inclined to take the position that Perl 5 is Perl 5 and if you want Perl 6 it’s that way →

That's because method resolution in Perl is based on the name of the method (or, sadly, the subroutine) and we can easily select the wrong method.

On what basis is it the wrong method? Perl 5 has never cared about the arguments in a call when figuring out where the call is going. By Perl 5 semantics it’s arguably the right method being called and the mistake in your code example is instead the fact that it has two implementations of the same method with mismatched signatures. (Or rather, UnlovedChild tries to inherit from incompatible parent classes. (Your naming choices make this sentence uncomfortably literal. (And I really hope they aren’t more autobiographical than I’m aware of.)))

While I can imagine several scenarios where multiple inheritance would come in handy, method resolution based on arity seems to be a bad idea in all of them.

If all three classes in question belong to the same project and are unlikely to be used somewhere else, then relying on a resolution method which only works if the methods in question happen to have a different number of arguments is indeed a code smell. Consider renaming DrunkenMother::shout to echo instead.

If, on the other hand, at least one of the parent classes leads a life of its own, then your code will shake when one day the authors of MissingFather add an optional argument to their shout method (using Perl 5 subroutine signatures syntax here):

class MissingFather  {
    method shout($volume='loud') {  # How does Ovid get
       say $volume ?                # his code so nicely
           "I'M OUTTA HERE!!!" :    # coloured?  I couldn't
           "I'm outta here!" }      # figure it out, so it's
}                                   # all black and white.

The authors of DrunkenMother might like that idea, and add the same optional argument to their shout method. By the way: Optional arguments are, in my view, one of the really, really good features of Perl.

So, for a call like $unloved_child->shout($scalar) you have to chose between a method which has zero or one arguments, and a method which has one or two arguments. At runtime, because Perl is an interpreter. This makes errors from bad method resolution show up way to late. When UnlovedChild is released, everything is just fine. Years later, when some innocent user of UnlovedChild updates one of the parent classes, because they need the $volume setting in another corner of their code, suddenly the parts using UnlovedChild starts to behave badly. And then they need to do what OO should protect against: They need to care about implementation rather than interfaces.

So, in that second scenario, the answer to "should we do this?" is, in my opinion, NO. And since the language doesn't know about the scenario, this is a general NO: We should not do this.

How would variadic methods work?

method shout(@args) { say "$message!" }
method shout($arg1, $optional_arg) { say "$message!" }

At the point that the top is prohibited, it would seem like your symbols are unique regardless of arity which makes me question the whole approach? Without variadic methods, how would old perl code move to this approach?

Leave a comment

About Ovid

user-pic Freelance Perl/Testing/Agile consultant and trainer. See http://www.allaroundtheworld.fr/ for our services. If you have a problem with Perl, we will solve it for you. And don't forget to buy my book! http://www.amazon.com/Beginning-Perl-Curtis-Poe/dp/1118013840/