develooper Front page | perl.perl5.porters | Postings from February 2016

signature subs and @_ semantics redux

Thread Previous | Thread Next
Dave Mitchell
February 29, 2016 18:47
signature subs and @_ semantics redux
Message ID:
On Thu, Dec 03, 2015 at 03:21:25PM +0000, Dave Mitchell wrote:
> What should the semantics of @_ be for a sub that has a signature?

Here's another stab at the semantics of @_ suppression and signatures.

I've divided it into two sections.  First what I think think all the
issues and proposed solutions are (notionally without my personal
preferences/prejudices showing too much), and second, what I personally
think we should do.

Section I: the issues

A: what should @_ be/contain at runtime if suppressed? Note that this
issue can't be avoided, due to all the weird ways it can be accessed, such
as 'local *args = $::{_::};'. Options:

    1 same as &f; i.e. remains that of the caller.

      pros: it's the fastest option
      cons: action at a distance; especially several nested @_-suppressed
            subs, where the inner one will see the @_ of several
            callers above.

    2 On entry, each sub does the moral equivalent of
            local *_ = $some_tied_array_that_croaks_on_access;
      This special array might be either global, or unique to each sub
      (like @_ is currently unique to each sub).

      pros: at runtime, will quickly flush out unintended uses of @_.
      cons: relatively slow to set up and take down;
            has issues with '&foo' and '&goto foo' (see C below).

    3 On entry, each sub does the moral equivalent of
            local *_{ARRAY}'
      which would initially leave the AV slot of PL_defgv as NULL.
      Any usage of @_ within the sub would auto-vivify a new @_, which
      would be discarded on sub exit. (Aristotle suggested this.)

      pros: in terms of speed it's somewhere between A1 and A2;
            it has an obvious behaviour with '&foo' and '&goto foo':
            i.e. they're called with no args, or with whatever @_ has
            been set to (also see C below).
      cons: stray uses of @_ at runtime won't be detected (but see B below)

B: what should accesses of @_ in the sub do at compile time?

    Aristotle suggested that in the presence of @_-suppression,
    'use strict "vars"' should trap @_ usage; or to put it another way,
    in that scope, @_ becomes a non-special-cased var for 'strict var'
    purposes, and so needs to be referred to as @::_ or $::_[0] or 'our
    @_' to avoid compile-time errors.

    Although Aristotle didn't say it explicitly, I'm assuming that
    this extends to things like 'shift' etc; so 'shift' within a sub is
    assumed to be short for 'shift @_' and so gives a compile-time error
    unless written explicitly as 'shift @::_'.

    This of course doesn't cover all possible accesses of @_, but is
    likely to pick up most of the common ones.

    '&foo;' and 'goto &foo;', if regarded as being short for
    '&foo(@_) and a hypothetical 'goto &foo(@_)', might also be
    subject to this stricture:  see (C) below.

    pros: detects some common coding errors, especially where existing
          code is being converted to signatures plus  @_-supppressed.
    cons: I dunno really - perhaps some people won't like writing
          '@::_ = (1,2,3)'?

C: what should C<&foo;> and C<goto &foo> do under @_-suppression?

    The two obvious sets of semantics are:

    1 &foo is equivalent to &foo(@_), i.e. they use the current
      value of @_, and whatever of A and B above are selected will
      affect the compile-time and run-time behaviour of trying to use the
      current value of @_.

    2 Where @_ hasn't been modified, there is an implied assumption that
      &foo and goto &foo reuse the args that were passed to the existing
      sub; in that interpretation, with these:
            sub bar($a,$b) { &foo;  }
            sub bar($a,$b) { goto &foo }
      bar would be expected to pass ($a,$b) rather than () to foo.

      The main con of (C2) is that it quickly becomes inconsistent:
            sub bar($a,$b) { &foo;  }        # foo($a,$b)
            sub bar($a,$b) { shift; &foo;  } # foo($a,$b) or foo($b)?
            sub bar($a,$b) { @=(1); &foo;  } # foo($a,$b) or foo(1)?

     The main pro of (C1) is that that is has a consistent behaviour;
     it's main con is that people might think that (C2) is logical
     behaviour. This might be less of an issue if the latter part of (B) is
     implemented; that is, &foo; would be regarded as being equal to
     '&foo(@_)'; and so dies under 'strict vars' unless explicitly written
     as '&foo(@::_)' This would force coders to explicitly write what they
     mean to happen.

     'goto &foo;' is is more problematic as there's no way (as far as I'm
     aware) to write 'goto &foo(@::_)'. So in this case people would
     have to explicitly write
        no strict 'vars'; goto &foo;
        our @_; goto &foo;

    3 Then there's the issue of whether the conceptual 
        local *_ = $some_tied_array_that_croaks_on_access;
          local *_{ARRAY}
      at the start of the sub should be unlocalised prior to doing goto

D: should signatures and @_=suppression be orthogonal.

    1 Various people have suggested that @_-suppression should be
      orthogonal to signatures.
      pros: the main advantage of this (if I'm summarising people
            correctly) is that it allows, when converting existing code to
            signatures, to say "this file has signatures but still has
            @_", so that missed 'shift', '&foo' etc still work.
     cons:  extra complexity; (B) would already allow most such uses to be
            detected at compile time

    2 Assuming orthogonality, there's also the issue of whether
      'use feature "signatures"' on its own should do @_-suppression;
      i.e. the current default should change.

      pros: when enabling signatures, people get the performance benefit
            of @_-suppression by default;
      cons: we're changing the existing behaviour of signatures (but
            con con, they are experimental).

    3 What should @_ suppression mean outside the scope of a sub?

      Assuming it's done by a pragma, then what should this do?:

        no snails; # or whatever the pragma is

        sub f {
            shift; # will do whatever we agree for A and B above

        shift; # what happens here?

    4 Assuming its orthogonal, what syntax should be used to enable

        * a new pragma (to be be bikeshedded), e.g.:

            no snails;

        * a modification of the existing feature pragma; e.g.:

            use feature 'signature';     # sigs and @_ suppressed
            use feature 'arg_signature'; # sigs and @_
            no feature 'arg_signature';  # no sigs; @_
            no feature 'signature';      # no sigs; @_

            NB: this wouldn't allow the 'no sigs, no @_ combo'

        * a per-sub pragma, e.g.

            sub foo :arg_array(1) {} # default - @_ is populated
            sub foo :arg_array(0) {} # @_ isn't populated
            sub foo($a,$b) :arg_array(0) {} # default: @_ isn't populated
            sub foo($a,$b) :arg_array(1) {} # @_ is populated

        * something in the signature syntax itself to indicate @_
          should be provided for this sub.

          This also wouldn't allow the 'no sigs, no @_ combo'

E: Under @_-suppression, would there be ways of accessing (parts of) the
sub's arg list?

   1. We could not bother; if people really need it they can avoid

   2. It could be provided as a new field returned by caller()
      (Note that caller already does something similar if called from the
      package DB: it populates @DB::args with a (non-ref-counted) copy
      of the caller's args, which is as buggy as a very buggy thing).

   3. New ops could be provided which access arg elements or slices etc,
      then XS modules could provide access.

F: For 5.24, should we warn in perlsub.pod and perldelta that @_ may

    Any changes aren't going to happen for 5.24, but if we reach rough
    consensus here on what *will* change (especially if by default
    signatured subs will no longer get @_), then should we update the docs
    to warn people not to rely on @_ etc (or whatever it is we agree)?

Section II: What I propose.

My own personal preferences are:

A3 and B - i.e. roughly what Aristotle proposed. This doesn't have too bad
performance impact, and has consistent behaviour with just a couple of
simple rules to remember, which are:

In the scope of 'no snails' (or whatever),

    1. Each sub has the logical equivalent of
            local *_{ARRAY}'
       at its start; so @_ starts off non-existent, and will be
       autovivified if used, and freed at the end of the sub.

    2. @_ is no longer special-cased as regards 'use strict "vars", and
       so gives a compile-time error like any normal array unless
       used as '@::_', $::_[0]', or 'our @_' etc.
       Implicit uses of @_, such as in 'shift', '&foo' and 'goto &foo' are
       treated as using @_ too, so they will need to be rewritten as
       'shift @::_', '&foo(@::_)', 'our @_ = (,...); goto &foo' etc.

These would be coupled with C1, which then provides predicable behaviour
for things like '&foo;'.

For C3, the local '*_{ARRAY}' shouldn't be unlocalised when doing goto

D: as for orthogonality, I still think its overkill, especially with
B providing compile-detection of most existing usages of @_ when converting
exiting files, but if people strongly want it, I won't stand in their way.

For D2, I am *strongly* of the opinion that 'use feature "signatures"'
by itself should do @_-suppression by default, without the need for a
further pragma.

I don't have a strong feeling for what the pragma/attribute or whatever
should look like.

If @_-suppression is in scope outside a sub, then I think if (B) is
selected, then 'use strict "vars"' should still apply. Other than that, the
pragma should have no effect.

E: ways of accessing caller args when @_ is suppressed.

I'm of the opinion that this is not needed; if people really want it,
via caller() or new ops or whatever, then I'd prefer this to be something
that could be done (preferably by someone else) at a later date; unless
people feel that the mechanism must be made available at the same time
that @_-suppression is added to core.

F: for 5.24, I think we should warn in perlsub and perldelta.

Dave's first rule of Opera:
If something needs saying, say it: don't warble it.

Thread Previous | Thread Next Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at | Group listing | About