Beefy Boxes and Bandwidth Generously Provided by pair Networks
No such thing as a small change
 
PerlMonks  

curious behavior: why does it do this?

by perl-diddler (Chaplain)
on Nov 27, 2018 at 23:09 UTC ( [id://1226434]=perlquestion: print w/replies, xml ) Need Help??

perl-diddler has asked for the wisdom of the Perl Monks concerning the following question:

I playing with the syntax of ranges, like my $h={args=>[0..5]}, my $h={args=>[0..-1]} and a few others, I came up with a range that surprised me: my $h={args=>["0".."-1]}

Using P::P to print the value, as it's made for cases like this (1st, the more normal cases, then the odd one:

> tperl #alias tperl='perl -we'\''use strict; use P +;' my $h={args=>[0..5]}; P "h=%s", $h; ' h={args=>[0, 1, 2, 3, 4, 5]} > tperl my $h={args=>[0..-1]}; P "h=%s", $h; ' h={args=>[]} ### then I tried: > tperl my $h={args=>["0" .. "-1"]}; P "h=%s", $h; ' h={args=>[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17 +, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, +52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 6 +9, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86 +, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]} # (and) > tperl my $h={args=>[q(00) .. q(-1)]}; P "h=%s", $h;' h={args=>[00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, +15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 3 +2, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49 +, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, +84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]}

So a quoted 0 started with 0, and a double zero ended up with a leading zero for 0-9, but the odd part was the "-1" turning into "99".

Huh? That's weird. Why does it do that?

Thanks...

Replies are listed 'Best First'.
Re: curious behavior: why does it do this?
by haukex (Archbishop) on Nov 27, 2018 at 23:33 UTC

    See the docs:

    The range operator (in list context) makes use of the magical auto-increment algorithm if the operands are strings. ... If the final value specified is not in the sequence that the magical increment would produce, the sequence goes until the next value would be longer than the final value specified.

    The magic string increment would normally produce the sequence "0", "1", "2", ... "98", "99", "100", "101", ..., and "-1" is not in that sequence.

    Update 2019-08-27: See this node, which documents the behavior, as well as the change in the behavior as of Perl v5.32.


      Hmmm....When I tried 000 as a starting value, I got an empty array.
      Someone else started w/"1", and also saw no output.
      Weird.
Re: curious behavior: why does it do this?
by davido (Cardinal) on Nov 27, 2018 at 23:43 UTC

    The hashref stuff is not actually even part of the necessary build-up to demonstrate the behavior:

    perl -E 'say for "0".."-1"'

    This produces 0, 1, 2, and so on up to 99. But the '-' is not part of the necessaries either:

    perl -E 'say for "0".."^1"'

    Same outcome. And in fact, the '1' isn't needed:

    perl -E 'say for "0".. "##"'

    Same behavior. And it turns out that the number of digits on the right side is the only thing that dictates how many numeric digits we count up to:

    perl -E 'say for "0" .. "abc"'

    ...produces 0 through 999 as output.

    Why does this all work so well? Here's the explanation from perlop:

    If the final value specified is not in the sequence that the magical increment would produce, the sequence goes until the next value would be longer than the final value specified.

    However, I feel it's stretching the documentation for convenience sake to say that the fact that the strings are never upgraded to numbers (even the one on the left) but are iterated upon within the confines of numeric digits until they fill the number of digits represented on the right, is actually well documented. ;)


    Dave

      Why does this all work so well?

      Thanks for the excellent clarification and elaboration.

      However, given that print for "0" .. "-1"; outputs the numbers 0 through to 99, I'm puzzled (in an idle sort of way) as to why print for "1" .. "-1"; doesn't output the numbers 1 through to 99.
      In fact, the latter produces no output whatsoever and I find myself thinking that perhaps it doesn't really work all that well at all.

      (I'm running perl-5.28.0 on Windows.)

      Cheers,
      Rob
        I'm puzzled (in an idle sort of way) as to why print for "1" .. "-1"; doesn't output the numbers 1 through to 99.

        That is indeed quite strange, and I can't make sense of it at the moment... seems like a bug to me.

        $ perl -wMstrict -MData::Dump -e' dd "0".."-1" ' (0 .. 99) $ perl -wMstrict -MData::Dump -e' dd "1".."-1" ' () $ perl -wMstrict -MData::Dump -e' dd "01".."-1" ' ("01", "02", "03", "04", "05", "06", "07", "08", "09", 10 .. 99) $ perl -wMstrict -MData::Dump -e' dd "90".."-1" ' () $ perl -wMstrict -MData::Dump -e' dd "1".."xx" ' (1 .. 99) $ perl -wMstrict -MData::Dump -e' dd "11".."xx" ' (11 .. 99) $ perl -wMstrict -MData::Dump -e' dd "90".."xx" ' (90 .. 99) $ perl -wMstrict -MData::Dump -e' dd "-1".."xx" ' -1 $ perl -wMstrict -MData::Dump -e' dd "0".." -1 " ' (0 .. 9999) $ perl -wMstrict -MData::Dump -e' dd " 0 ".." -1 " ' () $ perl -wMstrict -MData::Dump -e' dd " 11 ".." -1 " ' () $ perl -wMstrict -MData::Dump -e' dd "0.0".."-1.0" ' "0.0" $ perl -wMstrict -MData::Dump -e' dd " 0.0 ".." -1.0 " ' () $ perl -wMstrict -MData::Dump -e' dd "0.0".." 1.0 " ' "0.0" $ perl -wMstrict -MData::Dump -e' dd " 0.0 ".."1.0" ' (0, 1)

        Maybe there's some kind of heuristic going wrong... for example, maybe the intention is that if both sides looks_like_number, then treat them as numbers instead of strings - but several of the cases above don't work like that. I dunno at the moment, this may need some digging in the sources. (I ran a bisect earlier, and it seems that at least the "1".."-1" case has always returned the empty list.)

        Update: Reported as Bug #133695

        Update 2019-08-27: See this node, which documents the behavior, as well as the change in the behavior as of Perl v5.32.

        ... why print for "1" .. "-1"; doesn't output the numbers 1 through to 99. ... (I'm running perl-5.28.0 on Windows.)

        And likewise 5.8 on Win7. And likewise puzzled.

        c:\@Work\Perl\monks>perl -wMstrict -MData::Dump -le "print qq{perl version: $]}; ;; my $h = { args => [ '1' .. '-1' ] }; dd $h; ;; print for '1' .. '-1'; " perl version: 5.008009 { args => [] }


        Give a man a fish:  <%-{-{-{-<

        *curiouser* and *curiouser*.... the 1st explanation sounded logical, until I read your post...now, not as sure.
      The hash was from the program I was testing, since the hash would be a blessed object holding, among other things, a 'range' for the acceptable number of parameters that could be entered.

      I.e. If I have a switch or option parser, say for the command line, and you want to have one option that can take 0 or more parameters, what would be a good syntax for specifying that?

      I was looking at the syntax of the range operator as a possible model for such an option, which is why I think I said I was playing around with the range syntax -- trying to think what my needs were, and how to make such a specification intuitive and useful.

      While the hash might not be directly useful, having the range specified in a dynamic array would be, as collected input could be stored there. Simply listing options for range syntax doesn't give me a feel for how such would be included in a data structure.

      If you put your examples into an array with other specifications named in a hash. Your examples might look more like what I was working with if you had them in a {}, since instead of (or in addition to), I have thought about specifying a min/max (but that didn't feel right) as well as one or more functions for validation and/or processing of the options on-input and perhaps for specifying a stopping condition.

      I mentioned that I used an alias -- because, at least while playing, I often start with 1-liners, or "few-liners" to get an idea on how something might look or might work.

      I hit the "-1" example when thinking about a way to specify "no maximum" # of params. Even specifying 0..5... looking at it I think 0 to 5 params (i.e. having no args would be ok too), but that's really not what that notation means...(*sigh*)

      Note: I'm not asking folks to solve that problem, just giving the context of how I ran into this and the reason for the form of the problem.

Re: curious behavior: why does it do this? (updated!)
by haukex (Archbishop) on Nov 30, 2018 at 14:20 UTC

    Based on the discussion Re^2: curious behavior: why does it do this?, I reported bug #133695, where I just posted some patches. One of them explains the behavior in Perls up to v5.30 of the range operator when its operands are strings:

    The range operator in list context can make use of the magical auto-increment algorithm if both operands are strings, subject to the following rules:

    • With one exception (below), if both strings look like numbers to Perl, the magic increment will not be applied, and the strings will be treated as numbers (more specifically, integers) instead.

      For example, "-2".."2" is the same as -2..2, "1".."-1" is the same as 1..-1 (producing the empty list), and "2.18".."3.14" produces 2, 3.

    • The exception to the above rule is when the left-hand string begins with 0, including the string "0" itself. In this case, the magic increment will be applied, even though strings like "01" would normally look like a number to Perl.

      For example, "01".."04" produces "01", "02", "03", "04", and "0".."-1" produces "0" through "99" - this may seem surprising, but see the following rules for why it works this way. To get dates with leading zeros, you can say:

          @z2 = ("01" .. "31");
          print $z2[$mday];

      If you want to force strings to be interpreted as numbers, you could say

          @numbers = ( 0+$first .. 0+$last );
    • If the initial value specified isn't part of a magical increment sequence (that is, a non-empty string matching /^[a-zA-Z]*[0-9]*\z/), only the initial value will be returned.

      For example, "ax".."az" produces "ax", "ay", "az", but "*x".."az" produces only "*x".

    • For other initial values that are strings that do follow the rules of the magical increment, the corresponding sequence will be returned.

      For example, you can say

          @alphabet = ("A" .. "Z");

      to get all normal letters of the English alphabet, or

          $hexdigit = (0 .. 9, "a" .. "f")[$num & 15];

      to get a hexadecimal digit.

    • If the final value specified is not in the sequence that the magical increment would produce, the sequence goes until the next value would be longer than the final value specified. If the length of the final string is shorter than the first, the empty list is returned.

      For example, "a".."--" is the same as "a".."zz", "0".."xx" produces "0" through "99", and "aaa".."--" returns the empty list.

    However, in case one of my patches gets accepted, it would change "0".."-1" from returning "0".."99" to returning the empty list, which would be consistent with 0..-1. In any case, the patches include the corresponding documentation, so hopefully perlop will get an update.

    Update 2019-08-27: As of Perl 5.32, the behavior has changed to the following (changes underlined):

    The range operator in list context can make use of the magical auto-increment algorithm if both operands are strings, subject to the following rules:

    • With one exception (below), if both strings look like numbers to Perl, the magic increment will not be applied, and the strings will be treated as numbers (more specifically, integers) instead.

      For example, "-2".."2" is the same as -2..2, and "2.18".."3.14" produces 2, 3.

    • The exception to the above rule is when the left-hand string begins with 0 and is longer than one character, in this case the magic increment will be applied, even though strings like "01" would normally look like a number to Perl.

      For example, "01".."04" produces "01", "02", "03", "04", and "00".."-1" produces "00" through "99" - this may seem surprising, but see the following rules for why it works this way. To get dates with leading zeros, you can say:

      @z2 = ("01" .. "31"); print $z2[$mday];

      If you want to force strings to be interpreted as numbers, you could say

      @numbers = ( 0+$first .. 0+$last );
    • If the initial value specified isn't part of a magical increment sequence (that is, a non-empty string matching /^[a-zA-Z]*[0-9]*\z/), only the initial value will be returned.

      For example, "ax".."az" produces "ax", "ay", "az", but "*x".."az" produces only "*x".

    • For other initial values that are strings that do follow the rules of the magical increment, the corresponding sequence will be returned.

      For example, you can say

      @alphabet = ("A" .. "Z");

      to get all normal letters of the English alphabet, or

      $hexdigit = (0 .. 9, "a" .. "f")[$num & 15];

      to get a hexadecimal digit.

    • If the final value specified is not in the sequence that the magical increment would produce, the sequence goes until the next value would be longer than the final value specified. If the length of the final string is shorter than the first, the empty list is returned.

      For example, "a".."--" is the same as "a".."zz", "0".."xx" produces "0" through "99", and "aaa".."--" returns the empty list.

Re: curious behavior: why does it do this?
by perl-diddler (Chaplain) on Dec 10, 2019 at 01:33 UTC
    One thing I noted that seems a bit perverse (or wrong). Is how I'm printing out the numbers 08 and 09, which under perl would fail as numbers. My print routine, P looks at what it is printing for list members and automatically quotes non-numeric entities, while not quoting numbers. So is it wrong to leave out those quotes around 08 and 09? I shouldn't feel too horrible, as Scalar::Util's function "looks_like_number" also think 08/09 are numbers -- as does perl itself:
    > tperl use Scalar::Util qw(looks_like_number); for my $i ("00".."10") { P "%s(%d) %s like a number", "$i", "$i", looks_like_number($i) ? "look +s":"does not look"; }' 00(0) looks like a number 01(1) looks like a number 02(2) looks like a number 03(3) looks like a number 04(4) looks like a number 05(5) looks like a number 06(6) looks like a number 07(7) looks like a number 08(8) looks like a number 09(9) looks like a number 10(10) looks like a number
    So nothing about invalid octal constants from perl. ... *Ouch*, on a whim, I tried inserting a 0 before the 10, thinking my list iteration _might_ stop at 8, I.e.:
    for my $i ("00".."010") {...
    I got an enumeration up through 999. Another unexpected result! Or is that really what others expected?

    Perhaps as a nod to sanity, using 010 (with no quotes) as an end-value did end at 8, but the entire sequence was numified (with the leading 0 missing):

    0(0) looks like a number 1(1) looks like a number 2(2) looks like a number 3(3) looks like a number 4(4) looks like a number 5(5) looks like a number 6(6) looks like a number 7(7) looks like a number 8(8) looks like a number
      So is it wrong to leave out those quotes around 08 and 09? I shouldn't feel too horrible, as Scalar::Util's function "looks_like_number" also think 08/09 are numbers

      As I explained here, literals in the code (Scalar value constructors) are different from what Perl's string-to-number conversion recognizes - 0+"08" is 8 but 08 is an illegal octal number (there is one exception; a bug that appeared in 5.30 but should be fixed by the next release). looks_like_number accesses the same internal function that Perl uses to tell if a string looks like a number.

      for my $i ("00".."010") {... I got an enumeration up through 999.

      I explained the rules for the range operator and magic string increment in detail here, the behavior you mention is consistent with those rules because "010" is not in the sequence produced by the magic string increment (only "10" is; compare that to "000".."010").

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1226434]
Approved by haukex
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others sharing their wisdom with the Monastery: (4)
As of 2024-04-24 05:51 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found