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

The other day I ran into something that I thought was a little odd. First off, we know that the ternary operator can be used as an lvalue as in:

$flag ? $on : $off = $value;

And in fact, this works too:

$flag ? @array1 : @array2 = qw/This that the other/;

This works too.......

sub testthis { $_[0] = "Hi mom.\n"; } testthis ( $flag ? $yes : $no ); foreach ( $yes, $no ) { print "$_\n" if defined $_; }

But things break down in this push example:

my $condition = int rand 2; my ( @array1, @array2 ); my $stuff = "This, that, and the other"; push( ( $condition ? @array1 : @array2 ), $stuff );

That example gives a big fat compilation error.

I wonder if it has anything to do with the fact that neither of these succeed in modifying the parameter:

# First example; nothing gets assigned to @array; my @array; sub testthis { @_ = qw/This that and the other/; } testthis ( @array ); # Second example; nothing gets assigned to @array1 or @array2. # But no compiletime errors are generated either: my $condition = int rand 2; sub testthis { @_ = qw/This that and the other/; } my ( @array1, @array2 ); testthis( $condition ? @array1 : @array2 );

In a way, it seems like the ternary behavior within push must be related to the behavior of trying to assign to modify the contents of an array passed as a parameter to a sub. But the push version generates a compiletime error, and the non-push version just doesn't perform the modification of the parameter.

Has DWIM broken down? Is this defined behavior? I haven't found the trinary / push behavior documented. Perhaps I've missed something, but it's interesting to ponder anyway.


Dave

Replies are listed 'Best First'.
Re: Trinary operator can't be used as first argument of push
by BrowserUk (Patriarch) on Dec 25, 2003 at 23:45 UTC

    You can do it this way

    push @{ $_ & 1 ? \@a : \@b }, $_ for 1 .. 10; print "@a\n@b"; 1 3 5 7 9 2 4 6 8 10

    And the reason

    print prototype( 'CORE::push' ); \@@

    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    Hooray!

Re: Trinary operator can't be used as first argument of push
by NetWallah (Canon) on Dec 26, 2003 at 06:19 UTC
    There are a few lines from perldoc perlsub that are relevant here. The issue is NOT with the push - rather it is with what happens to array args.

    "In particular, if an element $_[0] is updated, the corresponding argument is updated (or an error occurs if it is not updatable). If an argument is an array or hash element which did not exist when the function was called, that element is created only when (and if) it is modified or a reference to it is taken...
    ...Assigning to the whole array @_ removes that aliasing, and does not update any arguments"

    So, to do what you are attempting (which I personally feel is poor programming style), you need to manually vivify the elements you want updated, and you need to fill them one element at at time (cannot use array assignment).

    The code segment below illustrates this:

    # MODIFIED Second example; nothing gets assigned to @array1 or @array2 + the first time, but DOES the second time # But no compiletime errors are generated either: my $condition = int rand 2; sub testthis { my $i=0; # Note: assignment is element-by-element foreach $word( qw/This that and the other/){ $_[$i] = $word; $i++; } } my ( @array1, @array2 ); testthis( $condition ? @array1 : @array2 ); print "With Empty arrays setting $condition: @array1,\n @array +2;\n"; @array1 = qw/no longer empty one/; @array2 = qw/no longer empty two/; testthis( $condition ? @array1 : @array2 ); print "With NON-Empty arrays setting $condition: @array1,\n @a +rray2;\n"; #################################### ## Output ### With Empty arrays setting 0: , ; With NON-Empty arrays setting 0: no longer empty one, This that and the;

    Note - only the existing array indices are updated. New ones are NOT created.

    "When you are faced with a dilemma, might as well make dilemmanade. "
Re: Trinary operator can't be used as first argument of push
by grinder (Bishop) on Dec 26, 2003 at 15:47 UTC

    It is true that the trinary operator can be used as an lvalue. On the other hand, I can't think of a legitimate reason why one would want to do so (although I'm open to suggestions).

    As a first approximation, I think the expression is better written as

    if( $flag ) { $on = $value; } else { $off = $value; }

    Then again, the repeated assignments of the same value does look a little redundant. This could be recast as

    ${$flag ? \$on : \$off} = $value;

    Which, not coincidentally, looks remarkably similar to the problem of pushing to the correct array, and to my twisted mind, seems a bit easier to follow than the initial code. The fact that the code looks weird alerts you to the fact that something weird is indeed going on.

    But I digress. I posited that one should never use the ternary operator as an lvalue. When I see code that does this, it is usually a degenerate (in more ways than one) form of a hash. I would probably recast the two scalars as keys of a hash, which would give something like:

    $status{$flag ? 'on' : 'off'} = $value;

    That, to me, looks the clearest of all.

Re: Trinary operator can't be used as first argument of push
by diotalevi (Canon) on Dec 26, 2003 at 03:05 UTC
    This is easy - your first arg is an array dereference and its contents is a array ref.
Re: Trinary operator can't be used as first argument of push
by ysth (Canon) on Dec 26, 2003 at 21:18 UTC
    With regard to the modification of passed arrays, the @_ array isn't aliased to anything (well, really it is aliased to the operand stack) but each individual element is aliased to each individual parameter. So this works:
    perl -we'upd(@x[0..2]); sub upd { @_[0..2] = "a".."c" } print @x' abc
    but removing either slice fails. Note that array or hash elements that you pass need not actually exist to be aliased successfully.

    Just recently someone reported that constant folding is allowing something like this push((CONST ? @x : @y), "foo") to work. IMO, this is a bug and should not be relied upon.