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

Another of my many blindspots has risen up and bitten me.

I thought I understood grep $person_has eq $_,  $item {...} to mean:

search $person_has for the value of the current default variable (set in a previous statement) and return true if that matches $item.

I've also explored the only alternate meaning I've been able to conjure up... namely:

search through the string $person_has and return true if any part of that string would match any part of $item.

Clearly, my understanding is incorrect, but despite perusing perldoc, Intermediate Perl, Programming Perl, several estimable web resources, -MODeparse and, of course, many of the very numerous nodes found by Super Search comprehension eludes me.

FWIW, this snippet is what's giving me grief:

for $name(@names) { my $hash_key = $name; $person_has = $hash_Dref{$hash_key}; print "\t\t $name has $person_has\n"; # as intended +, thru this line for my $item(@required) { # incorrect b +elow; unless ( grep $person_has eq $_, $item ) { print "$name is missing $item.\n"; } else { print "$name has $person_has.\n"; } } }

...which reports total failure to match any $item to any element of @required. In other words, it executes line 7 in every case, even in cases in which line 6 clearly shows that $person_has includes an exact match for $item.

In the complete script -- which is a lengthy self-imposed elaboration on code developed in Chapter 4 of Intermediate Perl -- I use strict and warnings and receive no reports of either. Further, I have used <c>Data::Dumper<c> and the debugger to verify that the variables contain what I intended/expected.

Can some great teacher lift the scales of blindness from my eyes, by offering a clearer (to me!) explanation of the real meaning of the construct (or, if it pinpoints my misunderstanding, the proper construct)?

Replies are listed 'Best First'.
Re: (Mis)Understanding <c>grep...eq $_<c>
by ysth (Canon) on Feb 24, 2009 at 06:36 UTC
    grep EXPR, LIST executes EXPR for each item in LIST (with $_ set to that item), and returns a list of those items from LIST for which the EXPR is true. Or in scalar context, as provided by your unless(), it returns a count of the items for which EXPR was true. So your grep $person_has eq $_, $item returns 1 when $person_has eq $item and 0 otherwise, and the whole unless () is equivalent to if ($person_has ne $item). So you may shoot whoever wrote the code.

    Your "search through the string", "includes an exact match", make it sound like you are expecting some kind of string matching operator, while grep is really a list filter (though the arbitrary filtering expression may sometimes be a match operation). Try rereading grep with that in mind.

Re: (Mis)Understanding <c>grep...eq $_<c>
by ikegami (Patriarch) on Feb 24, 2009 at 06:45 UTC

    I thought I understood grep $person_has eq $_, $item {...} to mean:

    That's not valid Perl.

    search $person_has for

    grep $person_has eq $_, $item searches through the list after the comma ($item), not through $person_has. The first expression tells us what is being sought: elements equal to $person_has.

Re: (Mis)Understanding <c>grep...eq $_<c>
by wfsp (Abbot) on Feb 24, 2009 at 06:47 UTC
    grep EXPR, LIST
    Evaluates the BLOCK or EXPR for each element of LIST (locally setting $_ to each element) and returns the list value consisting of those elements for which the expression evaluated to true.
    So, in this case, grep takes a list and returns a list.
    my @list = (1, 2, 3, 4, 5); my @result = grep $_ > 2, @list; print "@result";
    3 4 5
    You could probably get away with something like
    if (exists $item_lookup{$name}){ print qq{$name has $item_lookup{$name}}; else{ print qq{$name is missing $item_lookup{$name}; }
    in your for loop.
Re: (Mis)Understanding <c>grep...eq $_<c>
by jethro (Monsignor) on Feb 24, 2009 at 06:49 UTC

    grep doesn't search in strings, it searches arrays or lists. It makes no sense to use it on scalars. You give grep a list, and an operation to execute on each element of the list. For example:

    my @list= (1,8,3,9,2); my @newlist= grep $_<5, @list; # @newlist would be (1,3,2);

    grep is sort of a loop and what you did was create a loop that always loops only once. So the following two lines are practically equivalent

    unless ( grep $person_has eq $_, $item ) { unless ( $person_has eq $item ) {

        Ooh, nice! That's eminently readable. Much more likely to survive a code review than aristotle's ex-bang-bang operator.

        Rather than

        my @part = ( '', ( 'admin' ) x!! $is_admin_link, ( $subsite ) x!! defined $subsite, $mode, ( $id ) x!! defined $id, ( $submode ) x!! defined $submode, );

        we would now have

        my @part = ( '', grep( $is_admin_link. 'admin' ), grep( defined($subsite), $subsite ), $mode, grep( defined($id), $id ), grep( defined($submode), $submode ), );

        I like it.

        • another intruder with the mooring in the heart of the Perl

      [grep] searches arrays or lists.

      I don't think so. Do you have any reason to believe grep knows anything about arrays?

      >perl -le"@a = qw( a b c d ); @b = grep { push(@a,'!') if !$i++; 1 } @ +a; print @b" abcd

      Contrast with for which does:

      >perl -le"@a = qw( a b c d ); for (@a) { push(@a,'!') if !$i++; push @ +b, $_ } print @b" abcd!
        Grep could have been called "filter" in array context.
        Most common form is @output = grep{anon_sub_returning_True_False}@input;
        This code:
        #!/usr/bin/perl -w use strict; my @a = qw( a b c d ); my $i=0; my @b = grep { push(@a,'!') if !$i++; 1 } @a; print "@b\n"; print "@a\n"; __END__ output: a b c d a b c d !
        Means take each item in @a and put it into the anon grep subroutine. If that subroutine returns true, then move that item to @b. Absent an explicit return statement, the return value of a Perl sub is the last statement. In this case it is "1", or always true. So this is the same as: @a=@b; as far as grep{} is concerned. This push statement gets executed but has no bearing upon the action of grep{}.

        So, YES grep{} is meant to process/filter lists! I would argue that putting this push statement inside the grep is a weird thing to do.

        grep does have a scalar context also! This is useful in some cases, here this is just a "dumb" example.

        my $num = grep{1}@a; print $num; ##prints 5 (num of elements in @a) ie, abcd!
        But this can be used to say count number of matches in @a.
        $num = grep {/some_regex/}@a;
        But of course most common use would be @a=grep{/regex/}@b; which moves any item from @b which matches regex to @a.

        Update:Perl will not allow the input list to be modified within the grep{} subroutine. This could create a circular endless loop and that just won't happen.

Re: (Mis)Understanding <c>grep...eq $_<c>
by trwww (Priest) on Feb 24, 2009 at 06:47 UTC

    To reiterate what ysth is saying...

    unless ( grep $person_has eq $_,  $item ) { ... }

    is identical to:

    unless ( $person_has eq $item ) { ... }

    except that it is slower and harder to read.