zgrim has asked for the wisdom of the Perl Monks concerning the following question:

Couldn't find this in the docs.
The following code outputs "ok" when using the norm list container, an array:
$ perl -E'my @X = qw(a b c); say "ok" if "b" ~~ @X'
I was expecting the following to behave the same. It doesn't.
$ perl -E'use constant X => qw(a b c); say "ok" if "a" ~~ X'

( Can be noted that 5.11 DEVEL34039 behaves the same. )
Is this a constraint of some sort or a plain bug ?
Some wrong assumptions on my part ?

-- 
perl -MLWP::Simple -e'print$_[rand(split(q|%%\n|, get(q=http://cpan.org/misc/japh=)))]'

Replies are listed 'Best First'.
Re: 5.10 smart match behaviour
by ikegami (Patriarch) on Jun 10, 2008 at 13:18 UTC

    To know what test ~~ will perform, we need to know what the arguments to it will be.

    Remember that X is a function or at acts least like a function, and that functions are evaluated in scalar or list context. To know what X returns, we need to know in which context it was called.

    By definition, binary operators (such as smart matching) operate on two single values. They impose a scalar context on their operands. Given that the comma operator returns its RHS when evaluated in scalar context, the context is made evident by the following test:

    >perl -E"say 'ok' if 'a' ~~ ('a', 'b');" >perl -E"say 'ok' if 'b' ~~ ('a', 'b');" ok

    That means

    say "ok" if "a" ~~ X;

    is the same as

    say "ok" if "a" ~~ scalar(X);

    So what does X return in scalar context? Well, it's undocumented, and I daresay it's possible that it could change at any time. You shouldn't count on it.

    One solution to your problem is to create an array.

    >perl -E"use constant X => qw(a b c); say 'ok' if 'a' ~~ @{[ X ]}" ok
Re: 5.10 smart match behaviour
by almut (Canon) on Jun 10, 2008 at 13:33 UTC
    Is this a constraint of some sort or a plain bug ?

    The table in the docs1 describing the match behaviour doesn't say anything specific about what the operator does if either side is a list, or how exactly it would treat the return value of a (constant) function... In other words, I wouldn't call it a bug. OTOH, you're not the first to be struck by this somewhat non-DWIM behaviour, and probably not the last...

    (Personally, I'd find it more intuitive too, if a list would work like an array in the context of the smart match operator... but I don't know if it's too late to make that change now — or what other reasons there might have been to want it behave the way it's currently implemented.)

    ___

    1 search for "Smart matching in detail" (unfortunately there's no respective anchor in that doc, so I can't link to it directly)

      And it would magically determine whether to call an arbitrary function between list and scalar contexts how . . . ?

      As has been pointed out, it's a binary operator (albeit a two-character-wide one) just like + so the operands are put in scalar context just like every other binary operator. If you want it to work as if called on an array(ref), explicitly provide it with one by wrapping with []. Otherwise you're creating an exception for this one op and breaking orthagonality.

      Update: Someone mentioned that there's other two-character-wide binary ops, and you know actually most of them are two chars. /me needs more caffeine . . .

      The cake is a lie.
      The cake is a lie.
      The cake is a lie.

        As has been pointed out, it's a binary operator (...)

        I know it is a binary operator, and I personally do know how to handle that...  I'm just saying that it might be more intuitive if it would default to a (more DWIM) list context interpretation instead... (I bet this very problem is going to be a recurring issue here at PM).

        I so far haven't seen a convincing argument why it couldn't - in theory - behave that way ("not breaking orthogonality" is not sufficiently convincing, IMHO). A lot in Perl is concerned with making code do what people expect. And after all, there are other builtins, and at least one operator that can deal with lists just fine (think of (1, 2, 3) x 5).

        Sure, changing things will create conflicts with respect to the currently documented behaviour, but I wonder if it's primarily those scalar context variants that people will be thinking of using the smart match operator for, or if it's more the particularly neat implicit iteration behaviour that people will predominantly be wanting to use this operator for. In the latter case, it would be more natural if it could deal with lists without any special ado...  Just my five cents.

        (Ultimately, what's intuitive and what isn't is mostly an empirical or statistical question, so maybe we should have a PM poll on that?)

Re: 5.10 smart match behaviour
by Fletch (Bishop) on Jun 10, 2008 at 12:34 UTC

    A list (which is what your constant sub returns) isn't an array.

    Update: Perhaps this will help. Remember what a LIST in scalar context evaluates as.

    perl5.10.0 -E'BEGIN{ sub X () { say "wantarray: ", defined wantarray ? + (wantarray eq "" ? "empty" : wantarray) : "undef"; qw(a b c) } } say + "ok" if "a" ~~ X'

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      Did you just say "list in scalar context"? There's no such thing! His sub is *not* returning a list.

      Maybe you meant "the comma operator in scalar context", but that would presuppose that functions built by constant use the comma operator.

      In fact, it appears that the comma operator is *not* used. Quote the docs (emphasis in original),

      Note that constants with more than one value do not return their last value in scalar context as one might expect. They currently return the number of values, but this may change in the future. Do not use constants with multiple values in scalar context.

      That means that X (as built by constant) returns 3 in scalar context for the current version of constant, and that you shouldn't count on that always being the case.

        Oop, quite right.

        My constant stand-ins were using qw// directly which was behaving comma-like in imposed scalar context rather than passing through another layer (as happens with constant where the args get packaged up into an array and then turned into sub () { @list } which instead behaves like one would expect an array evaluated in scalar context imposed by the caller). More precise would have been "a sub whose return value is a list generated via qw// called in a scalar context", but that's not what he had due to how constant works now so he instead had "a sub whose return value is an array called in a scalar context".

        In either case they're both a single scalar value neither of which ('c' nor 3) match.

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

        ikegami++, thanks for pointing out very clearly what the problem was, in terms of contexts.
        But as almut++ also pointed out, i'm still thinking it's more a matter of non-DWIM behaviour.
        You'd say, well, we've got warnings for that. :)

        One of the problems is, if these are equivalent (because of the context imposed on X):
        perl -Mstrict -wE'use constant X => qw(a b c); say "ok" if "a" ~~ X'
        and
        perl -Mstrict -wE'use constant X => qw(a b c); say "ok" if "a" == X'
        that smart match breaks the warning:
        Argument "a" isn't numeric in numeric eq (==) at -e line 1.

        We could at least work towards a fix for this, maybe ?

        -- 
        
        perl -MLWP::Simple -e'print$_[rand(split(q|%%\n|, get(q=http://cpan.org/misc/japh=)))]'
      I didn't say the GV was an AV, the sub was an array, or anything internals in particular.
      I only expected smart match to be consistent (in a DWIM fashion).

      -- 
      
      perl -MLWP::Simple -e'print$_[rand(split(q|%%\n|, get(q=http://cpan.org/misc/japh=)))]'

        I think you're missing my point. It does seem to consistently evaluate the arguments on both sides in a SCALAR context if it's an EXPR and not an actual variable name (similar to how (say) keys requires something literally begining with a % to work upon). That means that your constant sub is being evaluated in scalar context returning the final element of the LIST, just as everywhere else you have a true LIST in scalar context.

        1 #!/usr/local/bin/perl5.10.0 2 use feature ":5.10"; 3 4 BEGIN { 5 sub ctx () { 6 my @caller = caller(1); 7 say "$caller[3]:$caller[2] wantarray: ", 8 defined $caller[5] 9 ? ( $caller[5] ? "yes" : "no" ) 10 : "undef"; 11 } 12 13 sub ret_scalar () { ctx; "a" } 14 sub ret_list () { ctx; @{[ qw/a b c/ ]} } 15 } 16 17 ret_scalar; 18 my $a = ret_scalar; 19 my @a = ret_list; 20 21 my $list_in_scalar = ret_list; 22 say "list in scalar: ", $list_in_scalar; 23 24 my @scalar_in_list = ret_scalar; 25 say "scalar in list: ", join( ", ", @scalar_in_list ); 26 27 say "match" if ret_scalar ~~ ret_list; 28 say "match" if ret_list ~~ ret_scalar; 29 say "match" if ret_scalar ~~ [ ret_list ]; 30 31 __END__ main::ret_scalar:17 wantarray: undef main::ret_scalar:18 wantarray: no main::ret_list:19 wantarray: yes main::ret_list:21 wantarray: no list in scalar: 3 main::ret_scalar:24 wantarray: yes scalar in list: a main::ret_scalar:27 wantarray: no main::ret_list:27 wantarray: no main::ret_list:28 wantarray: no main::ret_scalar:28 wantarray: no main::ret_scalar:29 wantarray: no main::ret_list:29 wantarray: yes match

        I will grant you that I couldn't find documentation in my quick once over explicitly documenting that EXPRs would be evaluated in scalar context. In any case the quick fix is to either change your constant sub to return an arrayref or wrap the call to the constant sub in an anonymous arrayref constructor.

        Update: Duur, as is pointed out below it's a binary operator so that's probably why it's not explicitly spelled out.

        Update the second: Tweaked ret_list to behave more like the sub generated by the OP's constant call than my initial version's sub ret_list () { ctx; qw/a b c/ } did; still doesn't match because of the scalar context imposed but the exact value that doesn't match is 3 rather than 'c'.

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

        It's impossible.

        I started writing a bunch of "if you do X, then problem Y surfaces", but the problem always boiled down to the need to differentiate between a scalar and a one item list. Perl is fundamentally unable to do that.

        Without the ability to differentiate between a scalar and a one item list, almost if not all matches become indistinguishable from

        @array ~~ @array

        While that's a useful test, so are all the test that would be lost.