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

Hi, I have a linked list object:
{ rest => $next_node, first => $data }
and a value() method that converts the linked list to a perl list:
sub value { my ($self) = @_; my @value = ($self->{first}, $self->{rest} ? $self->{rest}->value : ()); return @value; }
The reason I need the temporary @value is that sometimes value() gets called in a scalar context in which case it should return the length of the list, but if I was to just write
sub value { my ($self) = @_; return ($self->{first}, $self->{rest} ? $self->{rest}->value : ()) }
then in a scalar context the "," gets interpreted as the comma operator and the lhs of the comma gets thrown away, recursively, so the result is always zero.

I guess I could also write:

sub value { my ($self) = @_; return @{[$self->{first}, $self->{rest} ? $self->{rest}->value : ()]}; }
but neither solution seems totally satisfying.

Am I missing something?

Replies are listed 'Best First'.
Re: enforcing list context
by japhy (Canon) on Apr 26, 2006 at 11:24 UTC
    You'll have to return an array if you want scalar context to return the size. Otherwise, use wantarray and do something like:
    sub value { my ($self) = @_; return wantarray ? ($self->{first}, $self->{rest} ? $self->{rest}->value : ()) : 1 + ($self->{rest} && $self->{rest}->value) }

    Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
    How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart
      yes, that's probably the best solution.
      My first solution is poorer because of the temp @value, and the second because of the extra ref/deref on each call.
Re: enforcing list context
by BrowserUk (Patriarch) on Apr 26, 2006 at 10:42 UTC

    Try putting another set of brackets around the ternary:

    sub value { my ($self) = @_; return ( $self->{first}, ( $self->{rest} ? $self->{rest}->value : () ) ); }

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      I don't see how that could help, to be honest, the comma operator is still in scalar context.

        Sorry. Having re-read your question, I completely misunderstood the problem. The brackets don't help at all. Using the array seems like the best (only?) option to me.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: enforcing list context
by duff (Parson) on Apr 26, 2006 at 11:38 UTC

    I believe you could also do the following to always have list context on the comma:

    sub value { my ($self) = @_; return ()=( $self->{first}, $self->{rest} ? $self->{rest}->value : () ); }
      But that won't return anything in list context! The other problem is that you need to call value() with the same context each recursive time. I think breaking it down via wantarray() is the simplest solution.

      Jeff japhy Pinyan, P.L., P.M., P.O.D, X.S.: Perl, regex, and perl hacker
      How can we ever be the sold short or the cheated, we who for every service have long ago been overpaid? ~~ Meister Eckhart

        Ah, you're right. I was thinking that half a goatse op might do it, but that's what I get for thinking before my morning caffiene.

Re: enforcing list context
by TedPride (Priest) on Apr 26, 2006 at 17:18 UTC
    There is no need to do this recursively. In fact, that's the last thing you want to do, since each item requires an additional function call, and only a couple hundred simultaneous function calls are necessary to crash Perl. What you want is something more like:
    use strict; use warnings; my $r = 0; $r = { rest => $r, first => 'c' }; $r = { rest => $r, first => 'b' }; $r = { rest => $r, first => 'a' }; print join " ", value($r), scalar value($r); sub value { my $self = $_[0]; if (wantarray) { my @value; while ($self) { push @value, $self->{'first'}; $self = $self->{'rest'}; } return @value; } my $c; while ($self) { $c++; $self = $self->{'rest'}; } return $c; }
    It might be simpler to just have a master data item with a "length" value, however, since this would require constant time to retrieve rather than linear time, and would allow you to significantly simplify the above function.
      Yes, that's a definate option, but recursive solutions are often the most elegant to write.
      btw, I recently found a neat way around Perl's stack depth limits, it goes something like this:
      @_ = ($self->{rest}); goto &{ $self->can('value') };
      :-)
      update:
      sorry, that should have read
      goto &{ $_[0]->can('value') };
      -----
      perl -e 'print sub { "Hello @{[shift]}!\n" }->("World")'
Re: enforcing list context
by diotalevi (Canon) on Apr 26, 2006 at 20:50 UTC

    You could enforce list context like your title says if you really liked to.

    use Carp 'confess'; sub your_code { confess "List context is required" unless wantarray ... }

    ⠤⠤ ⠙⠊⠕⠞⠁⠇⠑⠧⠊

      I guess my title is probably somewat misleading, it probably should have read:
      "ensuring list context in an enclosing scalar context"
      -----
      perl -e 'print sub { "Hello @{[shift]}!\n" }->("World")'