Front page | perl.perl5.porters |
Postings from February 2018
Re: [perl #132760] Blead Breaks CPAN: YANICK/List-Lazy-0.3.0.tar.gz
Thread Next
From:
Nicholas Clark
Date:
February 17, 2018 10:26
Subject:
Re: [perl #132760] Blead Breaks CPAN: YANICK/List-Lazy-0.3.0.tar.gz
Message ID:
20180217100026.frgruzlk772pg4tv@ceres.etla.org
On Tue, Feb 06, 2018 at 10:38:40AM +0000, Zefram wrote:
> Sawyer X wrote:
> > It might actually be better to bite the bullet on this and say
> >"Okay, this one case really doesn't work. If you're using lvalue
> >subroutine attribute, you can't return in the signature." and leave it
> >as the much nicer syntax.
"can't return" - which would need to be enforced? At compile time? As a hard
error?
Before the recent change (with attributes after signatures) what happened
if one use a return in a signature for a function with an lvalue attribute?
A) error at compile time (presume not, but referenced below)
B) error at run time
C) silently does the wrong thing
?
and if we wanted to go back to the other syntax order
1) what of those three we do in the case of discovering the lvalue attribute
after we've compiled the code in signature?
2) would we ban all returns in signatures (to be consistent)?
Something about all this is bothering me a lot.
(And it's bothering me that I can't get it clear enough in my head to write
it without forward references.)
I think that it's a combination of two things. The second comes below, and
relates to this:
> Not only would current functionality be lost, but also a class of
> potential functionality that we're likely to want to explore. This is
> not an insignificant cost, and, as with the lvalue issue, was not taken
> into account in the decision to move signatures to precede attributes,
> because it wasn't specifically noticed at the time. (I eventually
> identified it in the @_-suppression thread.)
>
> -zefram
But the first is that we're seriously proposing to (re-)add a special case.
Yes, the implementation is full of them. But we should be striving to remove
them, not take the viewpoint that "another one isn't going to make much of a
difference". As Zefram writes in a later reply:
> There's also more value than this in permitting returning from a
> signature. Perl benefits from the popular pardigm of interchangeability
> of expressions, that any expression can go anywhere that an expression
> is permitted. A return expression is a type of expression, and it
> doesn't take any verbiage to explain, or neurons to remember, that it is
> permitted inside a while() condition, or in a sub parameter expression,
> or in any other particular place that takes an expression. But if it's
> not permitted in a default value expression in a signature, that's an
> exception to the general rule, which does need to be specially explained
> in the documentation, and does need to be specially remembered by users,
> to explain why their program doesn't work on the rare occasion that the
> nature of a subroutine leads them to put this type of expression in that
> type of expression slot.
And I think it very unlikely that folks are going to read the documentation,
or at least *remember* it at the time that they're writing the code.
Putting attributes first (at the cost of another special case to remember)
seems to be one of those places where we're (roughly) making it easier for
the "beginner" at the cost of making it harder for everyone else.
There's a Larry Wall quote/observation about this (which I can't find) to
the effect of being hesitant to make this type of design choice because it
actually penalises everyone (forever), *including* the beginners, because
they don't stay as beginners. (But I can't find it.)
The second thing that bothers me a lot is this:
> The potential that would be lost is to have attributes affect the
> compilation of a subroutine's body code. The :lvalue attribute is an
> instance of that kind of functionality. Another likely instance of it
> came up in one of the threads about @_ suppression.
[more detail]
> There's quite a problem here in that it looks somewhat inevitable
> for there to be some kind of per-sub control over @_ suppression.
> Lexical flags can set defaults, but there's always going to be
> the occasional exception. What we find here is that if subroutine
> attributes preceding the body didn't exist then it would be necessary
> to invent them. Currently we have them, but if they are abolished again
> by moving signatures in front of attributes again then this would create
> some design pressure to invent something new that does a similar job to
> attributes but appears before a signature.
Effectively here with lvalues we've already got a non-theoretical use case
of why attributes need to be known before the signature is parsed.
(Due to how perl 5 compiles code to optrees as soon as it's parsed. And
*that* isn't going to change without a 7-figure development budget.
The tokeniser is about 12K lines of code, op.c a further 17K. It's all
intertwined code with no real documentation, and enough corner cases that
almost any change will break *something* on CPAN, which requires a human
to work out which side needs to fix what.)
Zefram states another - any pragmatic implementation of @_ suppression is
going to want to change how the optree is generated, and the signature is
code and hence makes an optree.
Any other functionality that we might want to add that involves
1) argument parsing
2) code that wraps functions
3) optree generation
needs to be known about before the signature is parsed, which means that
whatever enables it has to come before it. Whilst most features make sense
to enable lexically, the ones that make sense *per-subroutine* (rather than
*per-scope*) belong as attributes.
I tried to think of stuff that we might want to do where they are most
logically enabled via (core) subroutine attributes and apply to the signature
too:
sub foo :inlineable {
...
}
to signal that a subroutine should be inlined (where possible, and hence
assume not to be redefinable or wrapped).
Likely implementing such a feature would need to compile to a different set
of template OPs, which can then be used to splice into caller subroutines,
but that requirement for template OPs would apply to a signature too.
and then likely rather hard to implement:
sub foo :multimethod(Bar: $bar, Baz: $baz) {
...
}
but the declaration of which arguments are part of dispatch likely can't fit
within the syntax of a signature, as Perl 5 doesn't have the colon available.
(Remember, Perl 6 changes the ternary op to C<?? !!> which frees up the colon
and hence lot of syntax possibilities which just can't work in Perl 5)
and given that the attribute has declared 2 (or more) arguments, they wouldn't
be repeated in the signature, but would affect argument processing, and hence
how @_ is handled and optimised.
For completeness, but probably not useful:
sub foo :cached {
...
}
a built-in Memoize equivalent. Which logically *should* wrap code in the
signature too, as signatures can make expensive computations, or hide this
as calls to functions which are expensive.
and also possibly not useful:
sub foo :method {
# implicit $self
...
}
sub foo :method(Bar: $this) {
...
}
as it's more logically covered by expanding signatures to take types (if
there is syntax that is workable) or a keyword.
But such an attribute could prefix the subroutine's code with a check that
the first argument is an object (or an object of the correct type), and
throw a meaningful exception if not, instead of generating a "cryptic"
"Can't call method "..." without a package or object reference" from the
code inside the signature or sub body. And again like the multimethod
attribute, one wouldn't repeat the argument declaration in the signature.
[Sawyer]:
[Zefram]:
> >This isn't the cost of not moving them. The cost is a narrow case of:
>
> I'm not clear what you're trying to say here. You seem to be trying to
> say that the situation I described doesn't apply to the lvalue issue?
> It would be incorrect to say that. I described costs of having to
> move parameter defaulting logic from a signature into the body block.
> Putting signatures before attributes of course does not mean that *all*
> defaulting logic would have to be moved. It only affects some subset
> of default value expressions, likely just those containing return
> expressions. Maybe even only on lvalue subs, though implementing that
> would be an extra level of trickiness because of the :lvalue attribute
> coming late. In the affected situations, the issues that I described
> would be free to occur.
I'm not sure either.
But there is a clear cost here of preventing a whole class of future
improvements. It's not just the narrow case of lvalue subroutines.
> >True, but it is an edge case.
>
> Getting the edge cases right matters. Getting them wrong means the
> abstractions are broken and users need to worry about whether the edge
> cases could occur.
Particularly if we can't detect the edge cases at compile time.
And even if we *can*, that might in itself be more implementation cost.
This one is not easy to balance either. It's already really damn hard to
maintain the parser code - there seem to be about 3 people total in the
world who are doing anything more than keyhole surgery bugfixes.
If we make trade-offs that increase this cost we may reduce this number
towards zero, which becomes a big problem as it's very hard to unwind and
hence get back out of the dead end.
Nicholas Clark
Thread Next