Skip to content
This repository

Signature vs no signature #26

Closed
barefootcoder opened this Issue June 13, 2011 · 30 comments

5 participants

Buddy Burden Michael G. Schwern thoughtstream Joel Berger Aristotle Pagaltzis
Buddy Burden
Owner

It looks to me like all these are the same:

method foo {}
method foo () {}
method foo (@_) {}

Should they be? Seems there isn't much point to the @_ signature if they are. Or is there some circumstance where they're different that I just didn't run across in my super-simple/stupid tests?

Michael G. Schwern
Owner
Michael G. Schwern
Owner

I didn't break the @_ case after all... or refixed it. Anyhow, it's tested now.

Michael G. Schwern schwern closed this July 05, 2011
thoughtstream
Collaborator

From my point of view, the semantics of:

method foo {}       # zero-ary
method foo () {}    # zero-ary
method foo (@_) {}  # any-ary

seems inconsistent with normal Perl 5 sub usage...and with Perl 6 usage
and, I'm told, with MooseX::Method::Signatures as well.

Personally, since you can't possibly be consistent with all three,
I would have expected Perl5-like behaviour:

method foo {}       # any-ary,  like: sub foo     {}
method foo () {}    # zero-ary, like: sub foo ()  {}
method foo (@_) {}  # any-ary,  like: sub foo (@) {}

However, I can see that making unsignatured methods default to ()
is definitely the safest alternative...but maybe it would be even safer
to issue a warning in that case and suggest the programmer
use an explicit set of empty parens?

Michael G. Schwern schwern reopened this August 11, 2012
Buddy Burden
Owner

Damn ... I started this whole discussion a year ago? Well, crap. Obviously I just had no idea WTF was going on, because I didn't really offer an opinion to how it should work. Well, after 11 months working with MS and MSM, I can say that I now do have an opinion. Let me see if I can make a case for it.

Let's say I'm working with MooseX::App::Cmd, and I write this:

    method usage_desc
    {   
        my ($command) = $self->command_names;
        my $extra = inner() || ($self->ACTIONS ? '<action>' : '');
        return $self->color_msg(cyan => "\n%c $command %o $extra");
    }

Seems fairly reasonable, doesn't it? Except it doesn't work. It gives me a "too many arguments" error, because something down in the guts of App::Cmd is calling this method with parameters. Now, I don't care about the parameters. I'm not even going to use them. Why should I have to add a signature for @_ when I don't even care what's in @_?

This is the sort of thing that was frustrating me when I actually started using MS and MSM. Obviously it's a very minor frustration, since it's easily fixed, but the fix is one of the few things you can write with MS which is 100% MS-specific: MXMS doesn't use that syntax, nor does Perl 6. So, being forced into this syntax is a bit weird to me.

Anyways, that's my thoughts on the matter. I think I'm also going to put together a blog post on the topic and see if anyone else wants to jump in.

Michael G. Schwern
Owner

tl;dr version: Just the fact that we're having this conversation says there's enough confusion about what no signature should do that it should be a parse error. The small convenience of being able to express () with a couple less characters isn't worth it. Unfortunately, its been in the code since last September. If we change it, we break backwards compatibility. Is a change worth it? Unless folks have evidence that this is a problem that's tripping folks up, no.

Now, here's why I think it should be () rather than (@_)...

The null signature is extremely Huffman encoded and thus valuable. As such it should do something valuable. The empty signature can do two sensible things. 1) It can be () or 2) it can be (@_). (@_) removes all value MS brings to the table. It should be very rare and very deliberate else MS has failed at its job. () is much more valuable. So from a Huffman encoding standpoint, it should certainly be ().

But what if the null signature means the user forgot to enter a signature? In that case it could 1) be () 2) be (@_) 3) be 1 or 2 but warn and 4) throw a parse error. If the user made a mistake, we want to let them know they made a mistake. (@_) fails utterly at this, it silently accepts any arguments. A parse error does the best job, but it renders the null signature useless for Huffman purposes. A warning might as well be an error, it's not worth adding a feature to turn off the warning, you'd do that by adding an explicit signature. () throws an error at runtime if the user passes arguments in, so they're told only if they use it wrong.

() is the best option because if the user put in no signature deliberately, it works as intended. If the user forgot a signature, it will error as soon as they pass in arguments (and if they never do, it works fine and no harm done). It either works as intended, or it throws an exception if it was used in error.

Buddy Burden
Owner

As promised (or perhaps "threatened" is a better word :-D ), I wrote a blog post on the topic. I think it probably explains better why I think it should be the way I think it should be, and also briefly addresses the issue of backwards compatibility.

If you want the tl;dr version, I would sum it up as follows:

  • No signature should mean no signature; I think it's what people will expect.
  • In this case, I think the MXMS compatibility (and, to a lesser extent, the P6 compatibility) is going to be more valuable than the backward compatibility.

But do read the blog if you can. It's not too long.

Joel Berger

FWIW, I like the idea of no signature implicitly means (@_) (which also shifts off $self). Since it shouldn't break any old code, seems like a no-lose and probably a win. Just my opinion.

Aristotle Pagaltzis
ap commented August 12, 2012

Fuctions with arity zero are rare in my experience, and even methods with arity zero uncommon. Conversely () is shorter and also far easier to type than (@_), so it is less of a burden to force the user to ask for the former than for the latter. In terms of Huffman, the obvious conclusion is for “nothing” to be taken to mean the latter.

But Huffman is only a moderately important language design concern. If you worship at its altar you are doomed to APL.

@barefootcoder’s argument is a better justification for why this should be changed.

Buddy Burden
Owner

Fuctions with arity zero are rare in my experience, and even methods with arity zero uncommon.

Oh I dunno; I'm using quite a few functions with 0 arity in my current script ... lots of callbacks. For some I use closures; for others, just a reference to a func with no args. And of course all accessors (which are not also mutators) have 0 arity.

Thanx for the cogent comments, Joel and Aristotle!

Aristotle Pagaltzis
ap commented August 13, 2012

That is a good point. If you already close over some state, you frequently need no further arguments passed in, so closures are likely arity zero. It is only non-inner named arity-zero functions that are rare. The only things that come to mind are stuff like the time builtin.

Still though, I remember encountering more than once a callback API where the callback would be called with data provided just in case, which most callbacks wouldn’t need, often because they could just close over it anyway. (E.g. passing the HTTP client object to a callback passed to a request method. Most of the time the callback is defined in the scope in which the HTTP client object is constructed, so it can just refer to the variable that the client instance is kept in, but sometimes you want to define callback generically and then it’s nice not to have to have a closure. That sort of thing.)

So I’d say even for closures it’s more sensible that the absence of a signature do what the absence of a signature is commonly taken to mean in other similar contexts, i.e. arity-any.

Michael G. Schwern
Owner

There's a very important bit that's been lost in this discussion: was no signature intentional or not? Does method foo {...} mean the user intended to express no signature (whether that means "no args" or "I don't care about the arguments") or does it mean they simply forgot to put in a signature? Both "no args" and "I don't care about the args" are fairly uncommon, so how it behaves in the error case is an important consideration. If we didn't have a compatibility issue with ourselves to worry about, I'd say it should just be a compile error.

If no signature means (@_), and the user made a mistake, no error is given. The method silently ignores its arguments. The mistake is difficult to trace. This is bad.

If no signature means (), and the user made a mistake, a runtime error will happen if arguments are passed in. The mistake is clearly pointed out. This is good. If no arguments are passed in, no harm no foul.

I really think (@_) is very uncommon, but maybe I'm wrong. I tried to do some searches to get data, but Ohloh Code Search thinks "(@_)" means I want to search for _. I see legit utility in these places...

  • pass through methods
  • things which are too difficult to express with a signature
  • I take no args, but I might get passed extra stuff anyway (as @ap pointed out above)

Part of the point of having signatures is to make things explicit.

Perl 5 compatibility means (@_) which means a user mistake is silent. I don't think its worth it, we don't have any actual backwards compatibility requirements with Perl 5. MXMS is trickier. We try to be compatible with MXMS. However, they can be compatible with us. :-) No signature in MS originally meant (@_) not because it was designed that way, but because that's what the implementation happened to do. If MXMS went that route, too, then they may never have thought the problem through. That no signature in MXMS sets $self but does not shift it suggests that.

Which is an important point: compatibility with MXMS actually means a third option. $self is set but @_ is not modified. This is possibly the least useful and most confusing of all the options. method foo (@arg) means my $self = shift; my @arg = @_ but method foo means my $self = $_[0]. It changes a fundamental feature of the method keyword: the invocant is shifted and you're left with the arguments. We don't even have a signature to express that.

In sum...

  • We have a backwards compatibility requirement with ourselves, which means any change has to be worth the deprecation cycle and existing code breakage.
  • No signature may be a user mistake, and that should result in an error if it was.
  • The MXMS behavior is bizarre, changing to match MXMS is likely out.
  • Perl 5 compatibility would be nice, but we don't actually have a requirement.
  • The presumption is that (@_) has limited utility (which could be wrong).
thoughtstream
Collaborator

Schwern pointed out:

If we didn't have a compatibility issue with ourselves to worry about,
I'd say it should just be a compile error.

I find I am in strong agreement with this position. If they use
Method::Signatures and don't bother to put a signature,
that's ipso facto a misuse of the module.

The main issue from my point of view is that, in terms of
"intentional programming", omitting the signature has two
plausible meanings and hence fails to clearly show intent.

So I'd prefer it was a parsing error and so couldn't happen in
the first place. I guess that makes my vote now for deprecate
and (eventually) retcon. ;-)

Michael G. Schwern
Owner

@barefootcoder brings up an important case in his blog post, the part about around, where he's using (@_) to mean "I take no arguments, but if I'm passed more just ignore them". I think this is a legit problem, but using (@_) is a poor solution. I've opened an issue to discuss that. #49

Aristotle Pagaltzis
ap commented August 17, 2012

If the user forgot to put a signature and the absence of a signature translates to (@_), then either he meant to declare some arguments in which case his code will simply break with a stricture error at compile time, or else he meant to declare no arguments, in which case all that happens is he gets a lack of error checking at incorrect call sites that pass arguments, likely with no ill effect on the operation of the program.

So yes, there’s some diagnostic lost. What is the likelihood and impact of adverse effects from that loss? Is the preservation of all possible diagnostics an overriding design goal that admits no compromise with other concerns – probably least of all, in that case, convenience? To me, thought given to @_ munging as in around or BUILDARGS is part of the quality-without-a-name in Perl APIs; should I have to always declare my intent? I would probably be using another language if I enjoyed being made to spell out every potential ambiguity explicitly for my own good.

It’s not difficult to be explicit about the signature if the programmer so chooses.

If they use Method::Signatures and don't bother to put a signature, that's ipso facto a misuse of the module.

No. Maybe for func. But it’s certainly not the case for method. Using that keyword just to have $self declared and shifted off of @_ with no further processing of the arguments is absolutely a valid use of M::S.

thoughtstream
Collaborator

But it’s certainly not the case for method. Using that keyword just to
have $self declared and shifted off of @_ with no further processing
of the arguments is absolutely a valid use of M::S.

But not an intentional use. I can't look at:

method foo {...}

and know what that means, unless I happen know what the default
behaviour is. More importantly, I can't look at that code and know that
the writer knew what it meant when they wrote it (unless I happen to
know that they happen to know what the default behaviour is, as well).

As we have canvassed elsewhere, there are (at least) four plausible possibilities:

  1. No signature, so no parameter checking, but $self created and populated.
    (i.e. same as method foo (@_) {...} would behave)

  2. No signature, so no parameter checking and no $self created and @_ unaltered.
    (i.e. same as sub foo {...} would behave)

  3. No signature, so no args allowed, and no $self created.
    (i.e. same as sub foo () {...} would behave)

  4. No signature, so no args allowed, but $self created and populated.
    (i.e. same as method foo () {...} would behave)

In my view, any usage that has four plausible meanings for the writer to
initially misunderstand, and for the readers to subsequently misinterpret,
is ipsis factis a mis-usage.

In contrast, requiring a signature on every func and method costs
developers very little extra effort (literally a few extra characters: () or (@_)),
but produces instant certainly whenever you look at the code. Pessimizing a
single write to optimize a 100 reads is a good tradeoff.

That's why I think an explicit signature is worth insisting on.
Even at the expense of breaking backwards compatibility.

Aristotle Pagaltzis
ap commented August 17, 2012

unless I happen know what the default behaviour is

So I guess the question it comes down to is, is it a reasonable expectation of a reader to know the language?

Pessimizing a single write to optimize a 100 reads is a good tradeoff.

To() add() an() explicit() I()-just()-want()-the()-default()-here() declaration() to() where() it() could() as() well() have() been() left() implicit() through() absence() pessimises() not() only() the() writes().

Otherwise I would not be here arguing about this.

Joel Berger

To pile on

I can't look at:
method foo {...}
and know what that means, unless I happen know what the default behaviour is.

but we know that sub foo {...} leaves the arguments in @_ so shouldn't it be reasonable to assume that method foo {...} does the same, except shift off $self?

Michael G. Schwern
Owner

I am squarely with @thoughtstream. Particularly because you are very rarely going to want to use the (@_) signature with Method::Signatures (if this assumption is in contention, let's talk about that). If you're using (@_) then either A) you're writing a pass through method or B) you're writing something too complicated to express with a signature. A is pretty rare. B is a failure of expressiveness on our part and should be fixed.

The whole point here is to explicitly declare what arguments your functions take. If all you want is my $self = shift and then unpack @_ manually you're really shopping in the wrong store.

Finally, are people really having trouble with this or are we bike shedding?

Joel Berger

The whole point here is to explicitly declare what arguments your functions take. If all you want is my $self = shift and then unpack @_ manually you're really shopping in the wrong store.

This is true if you are only writing one statement, however if you are declaring many methods, some with obvious signatures and some for which you either don't need arguments, or don't care, I'm not going to pull out sub { my $self = shift; ... } just because method is "too powerful"

Finally, are people really having trouble with this or are we bike shedding?

This I'm less sure about, but I still don't see why its an issue to allow a harmless use of an possible syntax. Its easy to document, as I said before method name {} behaves just like sub name {} except it first shifts off $self. Just because it doesn't have a signature doesn't mean that its not useful. Is this just about the name? Were it called Method::Simple would it matter that it doesn't have a "signature" to you?

Aristotle Pagaltzis
ap commented August 17, 2012

You're really shopping in the wrong store.

If that’s all you ever did, then sure. But if that’s what you want sometimes and not others, then shopping in the right store means loading both M::S as well as some other signatures module, then using two sets of keywords.

And to get back to this:

The method silently ignores its arguments. The mistake is difficult to trace. This is bad.

This is what sub has done for all its life. How often have you had bugs because of callers passing too many arguments who didn’t mean to (or picked the wrong callee and because of lack of sig checks, it didn’t blow up)? I cannot remember a single such incident in all my Perl life. Is this danger real?

Are people really having trouble with this or are we bike shedding?

Good question. @jberger seems to have run into it. I suppose if strictness is chosen and turns out to be a misdesign, there is always sub to fall back on, with a little extra typing. It would just be jarring in code that otherwise uses func and method everywhere.

Buddy Burden
Owner

Okay, wow, lots of good discussion. Let me see if I can address all the issues:

  • Are we bikeshedding?

I have to go with "no" on this one. At the time I asked the original question, back in June of last year, I didn't have a strong opinion. Honestly, I didn't care for the answer you gave me at the time. But I didn't argue, precisely because it would have been bikeshedding.

Now it's a year later. And I've spent the past year using MS and MSM in everything I can think of to jam it into. And this keeps coming up. I gave two examples (and those were real-world examples, taken from actual code), but those are hardly the only two. The point of MS is to make my life more convenient, and reduce my chance of creating bugs (or at least reduce the chance that I don't catch said bugs right away), and I keep having to go back and stick in (@_), which is neither convenient nor helping me find any bugs. If I didn't think this was an actual issue, I'd've kept my mouth shut and just gone with the original decision. ;->

  • Backward compatibility

I touched on this in my blog. If there's an actual case where this change could break existing code, I have yet to imagine it, and I challenge anyone else to do so either.

  • MXMS compatibility

Yes, it's technically true that what I suggest wouldn't be exactly the same as MXMS. But I still consider it compatible for one simple reason: currently, I'm converting MXMS methods with no signatures by just adding (@_), and it usually works.

  • No signature means either pass-through or impossible signature

No, not at all. There are also any number of cases where you just don't care about the arguments. Such as:

  1. Methods of classes which are essentially abstract base classes, so that the base class method just dies.
  2. Methods where the subclasses need some parameters, but the base class versions are simple defaults that don't require any.
  3. Internal methods (of Moose, App::Cmd, etc) where the base class provides args just in case you need them, but your subclasses often don't.

And, on top of that, I havent even found the pass-through to be that rare. Currying that can't be done with handles, shortcuts for getting at the second or more element of a returned list ... again, all of these are actual, real-world examples. I've used the (@_) signature nearly a hundred times in the past year, by quick-n-dirty wc -l.

  • no signature should be assumed to be a mistake

I have a number problems with this:

  1. What @ap said: merely getting the $self shifted off is plenty convenient.
  2. If I put no signature, I'd expect that to mean "no signature." Having it mean "empty signature," or (even worse) "compile-time error" seems to violate the principle of least astonishment.
  3. If you want to use what I said to make assumptions about what I meant, that's perfectly reasonable. But when you start trying to use what I didn't say to make assumptions, you're on much shakier ground. If the user didn't qualify a parameter name with a type, we'd never produce a "no type specified" error, would we? No, of course not. More importantly, we'd consider such a suggestion silly. This is the same thing, really.

In the end, this is a small thing, and quite possibly not worth the amount of time we've already devoted to quibbling over it. But, then again, small things matter, and often the little things are what make a module either so damned convenient you can't get enough of it or so damned annoying that it's not worth whatever benefits it might have. I'm not entirely sure which this is. But I do know that if I hadn't kept getting annoyed at having to type (@_) over and over again, I wouldn't have brought it up. Do with that as you will. ;->

thoughtstream
Collaborator

@ap asks (very reasonably):

This is what sub has done for all its life. How often have you had
bugs because of callers passing too many arguments who didn't mean to
(or picked the wrong callee and because of lack of sig checks, it
didn't blow up)? I cannot remember a single such incident in all my
Perl life. Is this danger real?

Not to developers of your calibre. But how many of those are there?

In my work as a Perl trainer, I am constantly astounded by the ways that
average programmers can mess up their code, can fail to understand the
language they're using, can inject bugs into the most unlikely places.
So I see bugs caused by unexpected argument-passing behaviour all day,
every day, in class.

You ask if we're bikeshedding. And, yes, we are...for us.
Every single person in this discussion is so far along to the right on
the Perl bell curve that it wouldn't matter--to us--what decision we
finally make about this. We'd learn the special case and adapt to it,
no matter what it was. And the fact that it's different from the
behaviour of sub, or of MooseX::Method::Signatures, or of Perl 6,
doesn't trouble us. We can cope.

But we ain't normal. :-)

The vast majority of Perl users are not like us. They really do struggle
with Perl on a daily basis. And every non-intentional feature of the
language, every inconsistency within a module or between modules,
only makes their struggle more difficult.

For them, predictability and intuitiveness are hard. They really do
need fewer default behaviours and more explicitness. That's why
Perl Best Practices is vastly more popular with regular Perl programmers
and much more contentious to experts. Because PBP mandates explicit and
intentional programming, and strongly discourages relying on default
behaviours that are obvious only to those who really deeply understand
the language (and not always even to them!)

To be honest, I really don't care what func and method do when
the signature is left off...because I would never do that.
It's just storing up misery for myself in 12 months time when I come
back to that code and have to dig out of my fading memory what this
particular default behaviour is. Being explicit removes the problem,
so that's what I'd always do.

But I also have to think about the vast majority of Perl developers,
and--for them--I do care what we decide. They will write code using
(what they think is) the default behaviour and at least 3/4 of them
will get it wrong, there being at least 4 semi-reasonable guesses
as to what the default behaviour is. Then that code will be deployed,
and at least 3/4 of those reading the code will misinterpret it for
the same reason.

So when there's a chance to force people to use an explicit, intentional,
and unmistakeable syntax, then yes, I'm all for it. Even at the expense
of two extra characters to write (and, yes, to read). And even at the
cost of breaking backwards compatibility.

I think getting the right answer here--the right answer for the
vast majority in the middle of the bell curve--really is important,
and not bikeshedding at all.

Michael G. Schwern
Owner

@ap @jberger Just to make sure we're all on the same page... (@_) is the existing signature for "my $self = shift; and let me unpack @_ manually". You don't have to revert to sub nor load a different module, you use the existing (@_) signature. The discussion isn't "can you do it", it's "should it be the default behavior".

Aristotle Pagaltzis
ap commented August 18, 2012

To be honest, I really don’t care what func and method do when the signature is left off… because I would never do that. It’s just storing up misery for myself in 12 months time when I come back to that code and have to dig out of my fading memory what this particular default behaviour is. Being explicit removes the problem, so that’s what I’d always do.

Hmm. Essentially PBP, no? Along with your comments about novices and the less adept (the latter being a label I really don’t like, whatever name you give it…) – maybe the reason we are arguing about it is that this in the class of the things best left to linters like Perl::Critic? Or else – maybe it should be a silenceable warning as a compromise… that option gives me the hives, much like Abigail, but I could live with it, if reluctantly.

Just to make sure we’re all on the same page…

Yes, I was aware of that. That is the little extra typing I referred to when I mentioned that in the worst case, the option to fall back to sub still exists (i.e. you have to shift off $self, which method would have taken care of for you).

Michael G. Schwern
Owner
Joel Berger

My point thus far has been, in the absence of doing something else (or dying) when there is no signature, lets make it as much like sub as possible. However:

We could also make override, and the other method modifiers, inherit the signature if no signature is given.

... if there is something useful, like this possibility, which can be done when no signature is given, then I'm all for it!

Ladies and Gentlemen, I want to congratulate us all, even at this point, on a productive and calm discussion. People are listening to each other and discussing rationally. Good job everyone!

thoughtstream
Collaborator

@schwern observed:

If some folks expect X and some folks expect Y then
the principle [of least astonishment] doesn't really
tell us anything useful.

On the other hand, the very fact that some folks expect X (no signature
means no args
), and some folks expect Y (no signature means any
args, with a $self autoshift
), and some folks expect Z (no
signature means any args, without a $self autoshift
), and some
folks expect Ω (no signature means a mistake, so compile-time
error
)...actually does tell us something useful: that there is no
universally obvious default.

And so a design principle we developed for Perl 6 might well apply here too:
if there's no universally obvious default, there should be no possibility
of default at all. That's why I'm in favour of the Ω option.

(Apart, of course, for the obvious reason that "The Omega Option"
sounds like a wonderfully bad 1970's SF movie:

They ignored his arguments.
They revoked his signature.
They even took away his shift.
But they forgot about...The Omega Option!!!!!

;-)

Buddy Burden
Owner

Man, I really never imagined this would end up generating this much interest. I'm having difficulty keeping up now. :-) Let's see if I can hit all the new points:

from @thoughtstream:

They will write code using (what they think is) the default behaviour and at least 3/4 of them will get it wrong, there being at least 4 semi-reasonable guesses as to what the default behaviour is.

Well, but that assumes that all four of the semi-reasonable guesses are the same amount of semi-reasonable, and I do not think that's the case. Not even close.

Even at the expense of two extra characters to write (and, yes, to read).

Well, just to be pedantic about it, it's 4 extra characters. But that's not the important bit. The important bit is that those 4 extra characters annoy me. Partially because I nearly always have to go back and add them after something calls my method and blows up, because it never makes sense for me to declare a signature on a method whose parameters I will not ever touch (not even to pass through), and partially because it's a bit of an odd duck in syntax, and it's the one thing that I can almost guarantee that anyone who's never used MS is going to look at and go: Hunh?

I think getting the right answer here--the right answer for the vast majority in the middle of the bell curve--really is important, and not bikeshedding at all.

I understand what you're saying about the uninitiated. However, we also have to consider that we have to convince the initiated to use the darn thing, or the uninitiated will never hear about it. The experts are the ones who blog, and answer the PerlMonks questions and the Stack Overflow questions, and teach the rest. So I don't think it's a good idea to ignore them either.

from @ap:

... maybe the reason we are arguing about it is that this in the class of the things best left to linters like Perl::Critic?

Actually, that sounds pretty dead on to me.

from @schwern:

I put this up front because I wanted to say that I don't get much opportunity to use Method::Signatures much, and you do. In that respect, your opinions are based on experience and thus more valuable than mine.

Which is to say, if it comes down to Schwern vs Buddy, you win.

Very kind of you to say. Hopefully we can all zero on an agreement without it having to come to creator's fiat, even if that fiat is totally in my benefit. :-D

I have to go with "no" on this one. At the time I asked the original question,
back in June of last year, I didn't have a strong opinion. Honestly, I didn't
care for the answer you gave me at the time. But I didn't argue, precisely
because it /would/ have been bikeshedding.

Sorry about that. I encourage you to call things out more often.

No, no, nothing for you to be sorry about. Perhaps I should have spoken up. But my thought at the time was, the best I could come up with would have been "gee, I don't like that as much, but I can't really explain why" and that hardly seemed like the best place to start a big debate over it. :-)

That's a little scary. What are those methods doing if they're doing nothing with the invocant? I can probably answer my own question: providing Moose defaults.

Remember, they don't have to be doing nothing with the invocant: they just have to be doing nothing with the other args. $self still works fine in MXMS. It's just that @_ won't be quite the same. So pass-throughs wouldn't work ... but then pass-throughs in MXMS don't work with no sig anyway. If I have a pass-through method in MXMS, it has a sig of (@args) or somesuch. All my MXMS methods that have no sigs are more of the "I don't care about the args" variety, such as I outlined in my last post.

  1. Methods of classes which are essentially abstract base classes, so that the base class method just dies.

I don't agree that an abstract base method doesn't care about its arguments. ...

Well, I see your point, but this is also not C++. I sort of like it in Perl that my subclasses can have different args from the base class, or from each other. It's not something I do all the time or anything, but I have used it before. Also, without any way to inherit a method signature, I'm going to have to specify the full sigs on the subclass methods anyway, so there's not much incentive to spell out args on a method that does nothing but die.

We could also make override, and the other method modifiers, inherit the signature if no signature is given. This introduces possible confusion with "no signature means something" because override foo {} would mean neither "I take no arguments" nor "I take all arguments" but "I take the same arguments as my super class".

That sounds good at first, but I wonder if it has unintended consequences. You say "same sig as the base class," but we're talking about method modifiers, and that only works for override (which is the least useful modifier). For the rest, it would be more like "same sig as the unwrapped method." Which implies that we're going to type check and arg check the params for the modifiers ... which we probably shouldn't. In some sort of bizarre case where a method had a before, an after, and an around, we'd be checking the same set of args 4 times, when Moose (theoretically, anyway) makes it impossible for the args to be different. That's pretty inefficient.

This leads to another problem with "no signature" meaning "empty signature": we're doing stuff the user hasn't asked us to do, perhaps at the cost of slowing their program down. That seems pretty bad. "No signature" meaning "compile-time error" doesn't have that issue, of course ... it's just annoying. :-)

  1. Methods where the subclasses need some parameters, but the base class versions are simple defaults that don't require any.

I'm not sure I understand, could you give an example?

Sure. In the base class:

method uri_for_add_listing (@_)  { return '/manage/ppl/add-listing/' }

and in the subclass:

method uri_for_add_listing (Company::Person :$person)
{
    if ($person and $person->is_insider)
    {
        return '/manage/cpa/insider/add-listing/';
    }
    else  # must be lessor
    {
        return '/manage/cpa/lessor/add-listing/';
    }
}

This particular example is only half real--we designed it this way, but we've never actually needed anything like the subclass method shown above. A simpler example would be something like this (totally real code):

# base class method
method send_company_receipt(@args)
{   
    return undef;
}   

# subclass method
method send_company_receipt (Company::Company :$company!, ArrayRef :$billing_periods!)
{   
    # oodles and oodles of stuff
    # :
    # :
}

In this case, we don't want the method to die if a subclass fails to override it, merely do nothing and return a value that indicates that it did so.

In a way, this is just an extension of my no. 1 (where the base class method dies). The subclass wants to do interesting things, using parameters, and the base class wants to boring things and doesn't care what the parameters are. Whether that's dieing or returning a default is probably not that relevant, so, in retrospect, I was probably wrong to separate them. (Which is my way of recognizing that you're going to have the same arguments against this class of examples that you had against the last class of examples, and I don't want to have the same debate twice. :-D )

  1. Internal methods (of Moose, App::Cmd, etc) where the base class provides args just in case you need them, but your subclasses often don't.

Could you give an example?

Well, there's a great example in my blog, specifically for App::Cmd::Command::execute. When I'm using MooseX::App::Cmd, the arguments passed in to execute are useless: I have other, more convenient ways to get that information. But, way underneath, App::Cmd is passing them anyway, 'cause it doesn't know I don't need 'em. Here's another one, this time involving Moose:

# constructing the object won't fail because business_model is lazy
# OTOH, we can't make it non-lazy because it depends on company
# but what we _can_ do is force the constructor to call the builder, and thus fail early
method BUILD (@args)          # no type checking here; we're not using @args anyway
{   
    $self->business_model;
}

Perhaps my comment is not completely clear for someone who can't see the rest of the code, but this is a class which contains two attributes: company, and business model. When you construct it, you pass in the company, and the business model is determined from that. But there are some companies (generally brand new ones) for which the business model can't be determined. That means this class is going to blow up sooner or later, and we'd rather it be sooner. Thus, the little trick above.

And, on top of that, I havent even found the pass-through to be that rare.
Currying that can't be done with |handles|, shortcuts for getting at the
second or more element of a returned list ... again, all of these are actual,
real-world examples. I've used the |(@_)| signature nearly a hundred times in
the past year, by quick-n-dirty |wc -l|.

Let's see some examples and if we can do better than @_.

Okay, here goes.

1) Simple delegation can be done with handles if the args are passed through unchanged (and unaugmented). For currying, if you want to add one (or more, maybe, not sure about more than one curried arg) argument at the beginning of the arg list, you can also do that with handles. But what if you need something more complicated than that? What if the curried arg should go in the middle or at the end? More importantly, at least in my case, what if the curried arg isn't a constant?

I have a BusinessModel class, which does all sorts of interesting things. Most of those things require a property (this is property like real estate, not property like attribute). Said property contains (i.e. has-a) business model, and calling a method of business model subclass A using a property that has-a business model B would be fairly tragic. So, for both convenience and safety, I have a PropertyBusinessModel class, which contains the property and its business model, automatically determined (like the company example above). The PropertyBusinessModel simply curries all its methods (well, almost all) back to BusinessModel, adding its property attribute as the first arg. Except that's not a constant, so handles can't handle it (PI). So, PropertyBusinessModel consists, after just a bit of housekeeping, almost entirely of a metric shit-ton of these:

method billing_name () { $self->business_model->billing_name( property => $self->property ) }
method billing_period (@args) { $self->business_model->billing_period( property => $self->property, @args ) }
method billing_rate (@args) { $self->business_model->billing_rate( property => $self->property, @args ) }
method billing_rate_id (@args) { $self->business_model->billing_rate_id( property => $self->property, @args ) }
method billing_state () { $self->business_model->billing_state( property => $self->property ) }
# and many, many, more

As you can see, some are pass-throughs and some aren't, but the only reason some aren't is because some don't take any args other than the property.

2) Let's say I have this method:

method billing_rate_id
(   
    Company::Company    :$company,
    Company::Property   :$property,
    Str                 :$query_date,
    Str                 :$pick_nm = '0',
    Str                 :$source_tp,
    CodeRef             :$source_picker
)
{   
    # oodles and oodles of stuff
    # :
    # :
    return ($rate_id, $rate);
}

Which is great, as long as I either want both values, or am willing to do something like:

my $rate_id = ($bmodel->billing_rate_id(property => $prop, source_tp => $prop->property_tp))[0];

But that's ugly, and potentially confusing, and, if I forget to do the proper list gymnastics, I end up with a rate ID of 2. All around, a poor solution. Easy enough, we'll just change that return line to:

    return wantarray ? ($rate_id, $rate) : $rate_id;

Perfect. Now getting just the rate ID when I don't care about the rate itself (that is, the dollar amount) is trivial. But what if I need the rate but don't care about the rate ID? Well, it's either back to the list gymnastics, or else we could simply:

method billing_rate (@args)
{       
    return ($self->billing_rate_id(@args))[1];
}           

Which is exactly what I did.

  1. What @ap said: merely getting the |$self| shifted off is plenty convenient.

No argument that it's convenient, we have (@) for that, but is it SO convenient that it should be the default? That's what's got me confused is not that people want the (@) behavior, but that they want it as the default.

No, I don't think his point was that it was so convenient that it should be the default; rather, he was pointing out that it was a counter-argument to the contention that if you didn't provide a sig, the only possible conclusion was that you screwed up. But, because just shifting off $self is so convenient, it's entirely possible that that's all you wanted. And that you provided no signature because you wanted ... you know, no signature. :-)

  1. If I put no signature, I'd expect that to mean "no signature." Having it mean "empty signature," or (even worse) "compile-time error" seems to violate the principle of least astonishment.

If some folks expect X and some folks expect Y then the principle doesn't really tell us anything useful. Though I'm willing to accept an argument that I'm the only on who thinks no signature means empty signature.

I'm not saying you're the only one. I'm just pointing out this: I'm saying that "no signature" should mean "no signature." You're saying that "no signature" should mean "empty signature." If you tell me that you expect that A == B, well, okay, I take your word for it. :-D But I challenge the contention that most people are not going to expect that A == A.

If the
user didn't qualify a parameter name with a type, we'd never produce a "no
type specified" error, would we? No, of course not.

If we were a typed language we sure would! In a closer analogy, if I wrote a module which provided a var keyword whose point was to allow typed variables like var Int $foo then yeah, I'd probably make var $foo an error. Otherwise why are you using the module?

But this is where we return to @ap's point. Maybe I'm just using the module to get rid of my thousands of lines of my $self = shift;. Isn't this one of the main points you brought up in your presentation on MS?

If you disagree and say that var $foo should be the same as my $foo then we've hit a fundamental design disagreement. I believe it can be expressed as:

Buddy: When possible, work like Perl 5 to match existing expectations.
Schwern: We're not in Kansas any more, let's rethink everything!

Well, again, your analogy is imperfect. If var $foo did something--anything!--that my $foo didn't (not that I can imagine what that might be, but hypothetically), then, yes, I might argue that var $foo shouldn't be an error. My argument is not so much that we need it to work like Perl 5, but more that people won't expect things that they never even typed to cause an error, and that there's a valid use case for it that just so happens to match Perl 5 expectations. Basically, the whole P5 compatibility thing is a bonus AFAIC. I do agree that we should rethink everything, in terms of everything being on the table for consideration and possible change, but obviously that doesn't mean that we should change everything, just because we can. ;->

from @thoughtstream again:

And so a design principle we developed for Perl 6 might well apply here too: if there's no universally obvious default, there should be no possibility of default at all.

I understand what you're saying. I'm just a little leery of "no signature" meaning something other than "no signature" ... I sort of like things to mean what they say. :-) Going back to your argument about newbie vs expert (and, yes, I know @ap at least will object to my terms, but for simplicity's sake), I may believe that the expert will expect saying nothing to mean that you should have said something, but I have trouble believing the newbie will. I think the newbie will expect that it will just DTRT ... that's what Perl always does. And, again, from the expert's perspective, you can make an argument that "the right thing" is to blow up, but I'm willing to be the poor newbie won't thank us for that.

Final thoughts:

Essentially, what I'm thinking at this point is that I'm going to move away from the (@_) sig altogether. It's just too MS-specific, which is a polite way of saying "too we-just-pulled-it-out-of-our-butts-ish." :-D I can drop MSM into MXD and everyone will understand the resulting code--except for (@_). Sure, MSM doesn't do some thing that MXMS does, but the code won't have those things (by definition). The only thing it would have that MSMX doesn't is that weird sig. If you understand MXMS sig, or P6 sigs, you understand all MS sigs ... except for that one. So I'm probably going to start using either (@args) (for pass-throughs) or (@stuff), or perhaps (@whatever), to indicate that I don't care about those args at all. In the latter case, that will engender me a pointless array copy, but at least it'll be understandable. So, in the end, having no sig blow up seems silly and mildly inefficient, at least for my purposes.

But, as @thoughstream points out, I can deal. I thought it was stupid having to put (@args) in MXMS for pass-throughs too, but I got used to it. It was moderately easy for me to figure out why it didn't work. But I too worry about the newbies ... just in the other direction. :-/ I picture something like: "What's this error? 'you have no signature'? Of course I have no signature! I don't need no stinkin' signature, so I didn't put one!! What the $@*#?!?!" Of course, none of us truly know what the newbies are going to expect, so we're all just theorizing here. My personal theory is that someone with no preconceived notions is going to assume that "no signature" means "no signature," because ... well, why would they assume otherwise? But I'm fully cognizant that I have nothing to back that up other than instinct.

Joel Berger

"What's this error? 'you have no signature'? Of course I have no signature! I don't need no stinkin' signature, so I didn't put one!! What the $@*#?!?!"

Again, I think the issue might come down to the name. Schwern wrote a module named Method::Signatures to give his methods signatures. Others come after and see the benefit of not just the signature, but the automatic invocant. When that becomes more of the benefit than the signature suddenly the name seems funny. Therefore when Buddy suggests allowing Method::Signatures with no signature Schwern says "but thats not using it right", to which Buddy (and I) say but this is useful in more places than just when signatures are needed.

I see both sides, but when it comes to predicting what the uninitiated are going to expect, again, I think they will expect it to do what sub does, except act like a method and shift off $self, leaving @_ untouched. Again, I ask if this module were named Method::Simple (does that name exist, if it does pretend it doesn't) would we care that there is a signature or not? If the stated goal of the module was to shift off the invocant, and as a side benefit you could declare a signature, wouldn't this be what we expect the behavior to be?

Buddy Burden barefootcoder closed this February 24, 2014
Buddy Burden

I've re-reviewed this issue, now that a) two years have passed, and b) we have, thanks to @thoughtstream, the ... sig. I still think I make some damn cogent points above (if I do say so myself :-D ), but I've kind of gotten used to ..., and it doesn't bug me nearly as much as @_ did, so I'm happy enough. And two more years of momentum makes it an even worse idea to change how it works now.

So let's stick with what we've got. I'm closing this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.