-
Notifications
You must be signed in to change notification settings - Fork 530
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Chained comparison evaluates tied scalar twice #17692
Comments
I consider a single FETCH to be correct user behavior. |
should this be a 5.32 blocker? |
This is already on the "Milestone 5.32.0" list -- which I take to be the 'blocker' list for 5.32. |
Relevant mailing list thread: https://www.nntp.perl.org/group/perl.perl5.porters/2020/04/msg257322.html |
I'm going to revise my position. The semantics do dictate the Instead, I am in favor of a documentation update as @davidnicol suggested. |
David Nicol suggested for the tied case that you capture the variable:
But, that assumes you know that something is tied, and the point of I'm not sure which semantics dictate what. The mailing list thread was all implementation details, and specifically not language details. The language can decide what it wants to do with a feature, although I take it the implementation is harder because this isn't tokenized differently and is actually just shoehorning it into old syntax. |
@xenu submitted a comment that didn't make it into this ticket. The operation of this new feature is not as clear-cut as people previously thought. Shouldn't we make it experimental in 5.32? Since I think all new features should be experimental, based on my previous school of hard knocks, I agree with him. |
On 4/16/20 11:34 AM, Karl Williamson wrote:
@xenu <https://github.com/xenu> submitted a comment that didn't make it
into this ticket. The operation of this new feature is not as clear-cut
as people previously thought. Shouldn't we make it experimental in 5.32?
Since I think all new features should be experimental, based on my
previous school of hard knocks, I agree with him.
I concur with Karl here; make it experimental.
|
It is documented that it does this.
Yves
…On Thu, 9 Apr 2020 at 13:55, Sawyer X ***@***.***> wrote:
I'm going to revise my position. The semantics do dictate the FETCH be
done twice. It's only correct. However, user expectation may fall short of
that.
Instead, I am in favor of a documentation update as @davidnicol
<https://github.com/davidnicol> suggested.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#17692 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAZ5R7UYUHG7CXOBVFA3LDRLWZSLANCNFSM4L5U4YIQ>
.
--
perl -Mre=debug -e "/just|another|perl|hacker/"
|
This how it is documented to behave, why should this be a bug?
Yves
…On Sat, 4 Apr 2020 at 10:31, brian d foy ***@***.***> wrote:
*Description*
A chained comparison in 5.31.10 appears to evaluate the middle condition
twice for a tied scalar.
*Steps to Reproduce*
use v5.31.10;
package UnstableScalar {
use parent qw(Tie::Scalar);
sub TIESCALAR { bless {}, $_[0] }
sub FETCH {
my $value = int rand 10;
say "Fetching scalar with $value";
return $value;
}
}
tie my $unstable, 'UnstableScalar';
say $unstable foreach ( 0 .. 2 );
say "Comparing----";
if( 5 < $unstable < 9 ) {
say "Found a value between 5 and 9";
}
The comparison short circuits just fine, but if the first comparison is
true, the tied scalar is evaluated again (so things such as Tie::Cycle
will unexpectedly progress or skip values):
$ perl5.31.10 ~/Desktop/test.pl
Fetching scalar with 2
$ perl5.31.10 ~/Desktop/test.pl
Fetching scalar with 0
$ perl5.31.10 ~/Desktop/test.pl
Fetching scalar with 9
Fetching scalar with 2
Found a value between 5 and 9
$ perl5.31.10 ~/Desktop/test.pl
Fetching scalar with 6
Fetching scalar with 9
*Expected behavior*
I expect that the scalar would only call FETCH once and reuse the result
in the second comparison.
*Perl configuration*
Summary of my perl5 (revision 5 version 31 subversion 10) configuration:
Platform:
osname=darwin
osvers=19.3.0
archname=darwin-2level
uname='darwin otter.local 19.3.0 darwin kernel version 19.3.0: thu jan 9 20:58:23 pst 2020; root:xnu-6153.81.5~1release_x86_64 x86_64 '
config_args='-des -Dprefix=/usr/local/perls/perl-5.31.10 -Dusedevel'
hint=recommended
useposix=true
d_sigaction=define
useithreads=undef
usemultiplicity=undef
use64bitint=define
use64bitall=define
uselongdouble=undef
usemymalloc=n
default_inc_excludes_dot=define
bincompat5005=undef
Compiler:
cc='cc'
ccflags ='-fno-common -DPERL_DARWIN -mmacosx-version-min=10.15 -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include -DPERL_USE_SAFE_PUTENV'
optimize='-O3'
cppflags='-fno-common -DPERL_DARWIN -mmacosx-version-min=10.15 -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include'
ccversion=''
gccversion='4.2.1 Compatible Apple LLVM 11.0.0 (clang-1100.0.33.17)'
gccosandvers=''
intsize=4
longsize=8
ptrsize=8
doublesize=8
byteorder=12345678
doublekind=3
d_longlong=define
longlongsize=8
d_longdbl=define
longdblsize=16
longdblkind=3
ivtype='long'
ivsize=8
nvtype='double'
nvsize=8
Off_t='off_t'
lseeksize=8
alignbytes=8
prototype=define
Linker and Libraries:
ld='cc'
ldflags =' -mmacosx-version-min=10.15 -fstack-protector-strong -L/usr/local/lib'
libpth=/usr/local/lib /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/11.0.0/lib /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/lib /usr/lib
libs=-lpthread -ldbm -ldl -lm -lutil -lc
perllibs=-lpthread -ldl -lm -lutil -lc
libc=
so=dylib
useshrplib=false
libperl=libperl.a
gnulibc_version=''
Dynamic Linking:
dlsrc=dl_dlopen.xs
dlext=bundle
d_dlsymun=undef
ccdlflags=' '
cccdlflags=' '
lddlflags=' -mmacosx-version-min=10.15 -bundle -undefined dynamic_lookup -L/usr/local/lib -fstack-protector-strong'
Characteristics of this binary (from libperl):
Compile-time options:
HAS_TIMES
PERLIO_LAYERS
PERL_COPY_ON_WRITE
PERL_DONT_CREATE_GVSV
PERL_MALLOC_WRAP
PERL_OP_PARENT
PERL_PRESERVE_IVUV
PERL_USE_DEVEL
PERL_USE_SAFE_PUTENV
USE_64_BIT_ALL
USE_64_BIT_INT
USE_LARGE_FILES
USE_LOCALE
USE_LOCALE_COLLATE
USE_LOCALE_CTYPE
USE_LOCALE_NUMERIC
USE_LOCALE_TIME
USE_PERLIO
USE_PERL_ATOF
Built under darwin
Compiled at Mar 27 2020 01:45:56
%ENV:
PERL="/Users/brian/bin/perls/perl-latest"
PERL5_PATH="/Users/brian/bin/perls"
PERL6_PATH="/Users/brian/bin/perl6s:/Applications/Rakudo/bin:/Applications/Rakudo/share/perl6/site/bin"
PERLDOTCOM_AUTHOR="brian d foy"
@inc:
/usr/local/perls/perl-5.31.10/lib/site_perl/5.31.10/darwin-2level
/usr/local/perls/perl-5.31.10/lib/site_perl/5.31.10
/usr/local/perls/perl-5.31.10/lib/5.31.10/darwin-2level
/usr/local/perls/perl-5.31.10/lib/5.31.10
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#17692>, or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAZ5R3YT56CGT33UJ4HXUDRK3V57ANCNFSM4L5U4YIQ>
.
--
perl -Mre=debug -e "/just|another|perl|hacker/"
|
The original documentation specified this.
Yves
…On Thu, 9 Apr 2020 at 13:55, Sawyer X ***@***.***> wrote:
I'm going to revise my position. The semantics do dictate the FETCH be
done twice. It's only correct. However, user expectation may fall short of
that.
Instead, I am in favor of a documentation update as @davidnicol
<https://github.com/davidnicol> suggested.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#17692 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAAZ5R7UYUHG7CXOBVFA3LDRLWZSLANCNFSM4L5U4YIQ>
.
--
perl -Mre=debug -e "/just|another|perl|hacker/"
|
I learned in my $work career that when the person charged with creating user documentation of my creations was troubled by some detail of how it worked, that meant it failed the DWIM test. And I was almost certainly wrong. (Sometimes I worked with requirements, and sometimes was given free reign, but usually the requirements failed to address the level of details involved.) Usually I was wrong because I had had my head down in the coding details and could not see the forest for the trees. I learned that by treating the documenter/coder-designer relationship as a collaboration that a better product resulted. (That was somewhat unusual in that company, as the writers were paid a lot less and considered lower class citizens by many.) I think we owe great deference to brian's opinions. He has proven himself adept at explaining perl to a wide audience. Certainly dismissing them is done at our peril. Hedging our bets by making the feature experimental for the first release is the only prudent course of action. I now can't remember a case where the documenter was wrong and I was right. Perhaps there were some where further explanation convinced them that it needed to operate the way I had designed it. I of course do remember the times when I was right but was overruled by management, not involving the documenter. (For example, I remember feeling satisfaction when we got sued by a famous singer who missed getting to a performance because he expected the feature to work as I originally had implemented it. They never told me the result of that lawsuit) |
I wonder if we could warn when tied variables are used in chained comparisons? If so I wonder how often that would be more prophetic than annoying? We'd only need to do that for a middle one. |
TBH I really don't see the problem. Both behaviors are reasonable, and one's expectations depend on wether you see chained comparison as one or two operations. |
That doesn't sound like a good idea. 99% of tied fetches are idempotent so this wouldn't be a problem anyway. Quite frankly, this whole discussion feels rather theoretical. The combination of circumstances to run into this seem incredibly unlikely. Ties are uncommon, non-idempotent ties are really uncommon and accidentally dealing with such a non-idempotent ties seems even unlikelier (given that passing it as an argument does exactly the assigning to a variable that solves this). I don't think this is a real-world problem. |
I started thinking about this because the confusion was one of the first reactions in Reddit to my post of the new feature. I didn't think of the Here's roughly how regular people are going to react to this feature:
This is quite a number of curveballs for one feature. It would be conceptually much better if it did call subroutines or methods twice, but it doesn't because the implementation leaks through a little. A persons knowledge of the chaining logical operators interferes with their understanding of this chaining operator. That they aren't homologues and can't be doesn't matter to the person who's already read more than they intended.. The best feature would stop at the "Very cool!" step. In this case, there are two more conceptual levels the reader must journey through, and then an extra bonus confusion. Even if they never encounter a Documenting things (this or something else) tends to not to be helpful as we think it is. Virtually no one reads the docs (wouldn't that make life so much easier), and those who do tend to stop when they think they have the answer. |
That's not an or, but and and. You can't return a tied value from a sub, unless it's an lvalue sub.
Why do you think this is weird? |
It's not that I think this is weird. It's that I think most people won't think of it that way because that's not how they think of the concept. I've said about all I can say on the subject without repeating myself. |
On 4/18/20 10:16 AM, brian d foy wrote:
I started thinking about this because the confusion was one of the first
reactions in Reddit to my post of the new feature
<https://www.reddit.com/r/perl/comments/fu3auk/chain_comparisons_to_avoid_excessive_typing/fmb29k0/>.
I didn't think of the |tie| at first because it's rare and uncommon.
But, that's not the issue. It's about what people expect and what they
get. None of these people care about what the implementor has to do to
satisfy the expectation.
Here's roughly how regular people are going to react to this feature:
* Very cool!
* Oh, it expands to two comparisons though—weird.
* Oh, but it doesn't call subroutines or methods twice. Okay, cool
again because that's the same as Python.
* Oh, but if the subroutine or method returns a tied thingy or an
lvalue, the value could be accessed twice.
* Okay, so don't do that.
This is quite a number of curveballs for one feature. It would be
conceptually much better if it did call subroutines or methods twice,
but it doesn't because the implementation leaks through a little. A
persons knowledge of the chaining logical operators interferes with
their understanding of this chaining operator. That they aren't
homologues and can't be doesn't matter to the person who's already read
more than they intended..
The best feature would stop at the "Very cool!" step. In this case,
there are two more conceptual levels the reader must journey through,
and then an extra bonus confusion. Even if they never encounter a |tie|
in their life, they know that the edges cases for this feature took
longer to explain than the feature itself. They don't want to think
about how rare or improbable this is, but if they read far enough
(unlikely, which is a problem), they discover this feature isn't what
they thought it was.
I'm not sure whether brian is arguing for *not* including this new
functionality in perl-5.32.0, but I do think it reinforces the argument
for introducing it as experimental.
Thank you very much.
Jim Keenan
|
I think it should call FETCH twice since it should also call overloading twice (is that even implemented? I must admit I haven't followed this in detail). That said, making it experimental can't hurt. |
imaginary complete starting over:
Constraints: chained comparisons must all be numeric or all be string. They
also have to be in the same order, that is, composed of operators taken
from one of these sets:
< <= ==
>= ==
lt le eq
gt ge eq
trying to mix comparators that are not all in the same set would be a
parse-time syntax error.
When the parser identifies a comparison chain, it recognizes this
v0 c1 v1 c2 v2 [ ... cn vn]
and does this with it:
for i ( 1 .. n ){
when vi is magical or otherwise more dynamic than a plain old scalar,
resolve it to either a plain old number or plain old string, depending.
apply ci to v(i-1) and vi; when false RETURN FALSE
}
TRUE
this approach would mean comparator overloading (is that a thing? wow)
wouldn't work, only number or string overloading.
Also, tied stuff would get called once.
Short-circuiting would continue to work.
What's the delta between what's in place now and the semantics I just
described? I think it's just the highlighted bit, and that the loop is
fully unrolled.
…On Mon, Apr 20, 2020 at 5:18 PM Vincent Pit ***@***.***> wrote:
I think it should call FETCH twice since it should also call overloading
twice (is that even implemented? I must admit I haven't followed this in
detail). That said, making it experimental can't hurt.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#17692 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAKCIPPF6TN4X6NT3N74JODRNTC4DANCNFSM4L5U4YIQ>
.
--
"Amageddon, enthen armagedfunki."
|
I understand the concerns of parties here, but after reviewing it once again - and then again - I consider the current behavior correct and expected from a consistency perspective. I understand a person might expect an optimization, but what I think we're discussing is for the optimization to become the proper use. That is, just because we might be able to use the variable only once and "cache" it for the statement doesn't mean we should consider that the syntax. Instead, the only consistency is understanding how the code should work without the optimization, which is in two steps. The effect of this is that consistency, a This reminds me of the (Even when you don't call it an "optimization", the practice of avoiding a double call is - essentially - an optimization.) |
Making this feature experimental and removing that status the next release costs nothing, but it ships as non-experimental and it happens to be not that obvious for users and needs to change (remember smartmatch) - it'd be a much harder way. |
Once it is experimental, it requires two releases as experimental. This means it will only be available as non-experimental in three versions (that is, three years), assuming it's not changed in any way. Making it experimental is meant to allow us to change it when we're still trying to figure it out. If we don't consider it changing, requiring people to wait for three releases (three years) to have it does not gain anything. The fact that it can confuse people doesn't justify either experimental or change in behavior or implementation. Making it run a function twice instead of once would make it incorrect. Caching the value of the While it can be confusing, it is correct and should stay put. |
I don't know about the positions of anyone else involved in this discussion
but I'm entirely satisfied with suggesting putting double-quotes around
tied string values and adding zero to tied numeric values in the
documentation of the feature as a workaround to avoid "double-fetch" issues.
When there's a big long dereferencing expression, does the expression get
evaluated twice or just the resolution of it?
print "element $i is in range\n" if lower <=
$outer->{$inner}{a}{b}{c}[$i] < upper;
or
print "element $i is in range\n" if lower <=
$outer->{$inner}{a}{b}{c}[$i]+0 < upper;
this seems like a less contrived scenario than explicitly tieing something
by the coder, given dbx modules and so-on.
Which only makes one database call?
…On Wed, Apr 29, 2020 at 4:36 PM Sawyer X ***@***.***> wrote:
While it can be confusing, it is correct and should stay put.
|
FYI: this issue made it to the frontpage of /r/programming: https://www.reddit.com/r/programming/comments/gdxe1p/should_x_foo_y_read_from_foo_once_or_twice_perl/ Of course, most of the commenters probably aren't Perl programmers but I think it's still interesting to hear what people think. |
For completion's sake, here's what happens with two somewhat related situations: https://perl.bot/p/efhuah Object overloads apply to the referenced object itself and so persist through any passing or copying of references. The numeric overload is called twice if both comparisons occur and can result in different comparisons each time. Variable magic applies to the variable itself and does not persist through any passing or copying. It also cannot affect the resulting value directly but could have arbitrary side effects. This get magic is also called twice if both comparisons occur. |
Given that, I think it would be easier to discuss this in terms of an object with overloading; it is both significantly more common, and can be the result of a more complex expression. |
The complicating factor there being, of course, it's possible to overload every comparison operator individually. |
Most of the reddit discussions don't seem to get that perl supports tied variables. They also seem to be quickly forgetting that in any language they smugly consider to be better, the right side isn't evaluated if the left side is false. After reading reddit and some of the above examples, I think I've come to the conclusion that I would actually be more upset if Most of the examples I've seen so far are a little contrived to simplify the explanation. I get that. Can anyone think of a practical example of where this might actually be an issue? So far, the only things I've imagined are some sort of DBIC code or some sort of Math::BigInt object. However, I'm not sure I can imagine what the code would be that would unexpectedly misbehave. In my day job, I honestly don't use many tied variables that change when accessed multiple times. If I did, I would probably emphasize to the coder that they better make the variable VERY clear and have a darn good reason to do so. i.e. |
As noted in the reddit thread, people are confused by the statement that the chained comparison is actually broken into two comparisons (
Document it like that much of the problem goes away because there's not an exception for the subroutine case. I think Python needs the same doc adjustment too. |
You wouldn't see it in a code review. You wouldn't even think that it was happening because the person writing that code isn't thinking about Then someone else, far away, uses that library and does something a bit goofy because they have some task where black magic makes sense. It's not rare that people do unexpected and unforeseen things with the interfaces someone provides them. Many people in the Reddit thread made the same conceptual mistake. They assume that they have control over their entire ecosystem and are responsible for all of the code, and that all code interacting with a feature will come from them. In reality, we know that we don't know how people are going to interact with our CPAN code, how other CPAN code might interact with our code, and so on. These effects can be far away from the code that we actually write. It's not about what we write and what we allow, but what the world writes and what they decide to tolerate. |
I briefly played Now that I cannot "un-see" it... the meaning of Although I'm not a committer, I feel like it's a big promise to say that the side-effect must also be consistent with the unchained version and perhaps that will end up give us less resource in the future to iterate on the improvement of chained comparison. When we see the unchained version like: Most of languages and their implementations do evaluate the And... if there is an language implementation that's not doing short-circuiting -- it is logically correct too. (Although I'll be yelling if my To me, maintaining the mathmatical correctness of |
Isn't code that does not meeting user expectation by definition a bug? |
My USD 0.02 is this:
In the extreme case, all the terms are function calls:
For this case:
I hope that the new syntax preserves these semantics. I see the whole point of this new syntax as eliminating the need to do the funcleft() is less than $value and $value is less than funcright() but i have to work hard to get that right (via funcval() is between funcleft() and funcright() meaning that you "naturally" want to refer to the value once. If I have to worry about double eval of the thing I'm checking, I see no need to use the new syntax. And if you want the double eval, you can drop back to the current syntax with two compares joined by an |
# flavor to taste
package Acme::is_between;
use Exporter 'import';
our @export = qw/is_between/
sub is_between{
my ($middle, $left, $right) = @_ ;
return ($left <= $middle and $middle <= $right) if ($left <= $right);
return ($left >= $middle and $middle >= $right) if ($left >= $right);
return ($left le $middle and $middle le $right) if ($left le $right);
return ($left ge $middle and $middle ge $right) if ($left ge $right);
0;
}
1;
…On Wed, May 6, 2020 at 10:16 AM Matthew O. Persico ***@***.***> wrote:
funcval() is between funcleft() and funcright()
meaning that you "naturally" want to refer to the value *once*. If I have
to worry about double eval of the thing I'm checking, I see no need to use
the new syntax. And if you *want* the double eval, you can drop back to
the current syntax with two compares joined by an and.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#17692 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAKCIPKTMYXSRLS6EEWBOR3RQF5MZANCNFSM4L5U4YIQ>
.
--
With great risk comes spectacular failure
|
@matthewpersico All of this is already specified to be true in the documentation. |
@Grinnz - quite frankly, I can't really tell if @briandfoy 's original concerns are being addressed or he code is being left as is from this long and twisty thread. Update: I missed #17692 (comment) But |
If I see
in a language, I expect it to be semantically equivalent to
because this is the implementation that results in the least processing, and it is actually a pain to write so the syntax shortcut is of the most benefit. (edit) |
All of this is already what happens. I suppose we can't do much about it now but anyone who sees this, please stop confusing the issue with the semantics of evaluating the expression only once, because that already the case. The question here is whether each side of the comparison should do a separate fetch if the central value is a tied variable. |
I'm closing the issue. Sawyer made his ruling and the continued discussion hasn't been well-informed. |
Well now I feel like my comment is being misread. If I write
and EXPR 1 is a tied scalar, that does call fetch at that point and results in a single fetch, as per the original statement in this issue. |
I did not see the part specifically mentioning tied variable semantics, my apologies. To respond to that, the difference here is that there isn't actually a temporary variable involved (as the documentation points out), only the same tied variable on the call stack, used in two operations. This would be a semantic explanation for it, to go with Zefram's technical explanation. Not that I have any opinion on which is the "right" answer. |
This is a loaded question. First of all, I didn't say "this code does not meet user expectations." I said, "it can confuse people." Two operative words: can and people, meaning it has the possibility of confusing and not everyone. Secondly, languages might confuse, but as long as they maintain consistency - on some level - it isn't a bug. To put it in other terms, Perl could have been consistent here by saying that in Consistency usually breaks perception. Sometimes one user and sometimes the other. This can be frustrating to either user, so it should be documented well. Not just the feature, but in which way is the feature consistent. The example of
It is not. I sometimes err in initial judgment. I consider it a human quality, though I wish I never made mistakes.
No offense was taken. I hope I addressed your comments fairly. FWIW, all of the authors I know on this thread are prominent in one way or the other. I appreciate and respect each of them and their positions and opinions. This is why this thread had me going back to the code, to the implementation, original thread, and to other core developers to discuss this again and again and again. I've tried explaining why I disagree with the experimental status in a different comment. It sums up to two reasons that go together:
Feature flags - especially experimental ones - are not free. They come at a price to the user and we should only consider them when apt. In this case, I don't see it. |
This is a fair point. I'll review the docs again tomorrow morning. |
That is already how it's documented. https://perldoc.pl/blead/perlop#Operator-Precedence-and-Associativity |
On 5/6/20 4:24 PM, Dan Book wrote:
That is already how it's documented.
https://perldoc.pl/blead/perlop#Operator-Precedence-and-Associativity
—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
<#17692 (comment)>, or
unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAA2DH46DJ6UST2HOZQMH2TRQHPSTANCNFSM4L5U4YIQ>.
I was the one who added the commit for this feature to blead:
commit 02b85d3
Author: Zefram <zefram@fysh.org>
AuthorDate: Wed Feb 5 07:43:14 2020 +0000
Commit: Karl Williamson <khw@cpan.org>
CommitDate: Thu Mar 12 22:34:26 2020 -0600
I did not know at the time that this would turn out to be a contentious
feature. But it has. The freeze date for contentious additions was
January 20. The PR also dates to after the freeze date.
That means that this commit violates our long-standing policy, and
doesn't belong in 5.32.
|
@xsawyerx apologies on the bluntness of my initial message, it was early and now that I've come back to read it--should have phrased it better. Anyway thanks for addressing the comment despite the initial loaded question. |
No, it's not documented like that. Instead of a general case, it starts off with the common case and presents a rule, then states a slightly less common case that uses a different rule, and then a rare case that doesn't seem to follow either rule. There's a way to unify those. The documentation starts as being two comparisons in the simple scalar case. That sets the frame for everything that follows. At that point, you have already told people how to think about this feature and it's the foundation for their interpretation of the following paragraphs. After that, it starts to list other cases and other behavior, all of which are framed as modifications to the initial case. Many people (as evidenced by the the reddit thread and this thread), aren't going to keep reading, and even if they did, they won't understand what they are reading or why it matters to their life. I think if we change the frame by documenting it the same way for all cases, we lose several paragraphs of exceptions and explanations. That is, for all cases, it's like |
I think if we change the frame by documenting it the same way for all
cases, we lose several paragraphs of exceptions and explanations. That is,
for all cases, it's like { temp = b; a < temp && temp < c }, even if the
particular implementation doesn't match that. Now, the subroutine and
scalar case are the
A chained comparison a < b < c essentially works like do { temp = b; a <
temp && temp < c }. When b is a magical overloaded or tied value, the
second fetch magic may be avoided by adding zero, for numeric comparisons,
or quoting, for string comparisons, the middle value, so temp will be a
plain r-value.
…--
With great risk comes spectacular failure
|
If the tied variable is fetched twice, it means this will sometimes be true:
And that seems bad. |
but this will keep the first result
… ( 6 < "$unstable" < 3 )
|
That would be possible with overloads even if tied variables were only fetched once. |
Description
The perlop docs describe the current behavior, so this doesn't contradict anything.
A chained comparison in 5.31.10 appears to evaluate the middle condition twice for a tied scalar but doesn't warn about it. Since a programmer might not realize they are using a tied variable, they might see odd behavior.
Steps to Reproduce
The comparison short circuits just fine, but if the first comparison is true, the tied scalar is evaluated again (so things such as
Tie::Cycle
will unexpectedly progress or skip values):Expected behavior
I expect that the scalar would only call
FETCH
once and reuse the result in the second comparison.perlop
says this may happen, but perhaps a warning would be useful here. Ideally, tied variables would be evaluated once.Is there a chance that the internals could recognize this and use the tied object instead of the scalar itself? This works as expected:
Perl configuration
The text was updated successfully, but these errors were encountered: