http://qs321.pair.com?node_id=11122227

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

Using Perl 5.26.1 under Linux I want to use a variable in the searchlist of a tr operator expression. The example on p.76 of the camel book, has two things wrong with it

a) eval " ... code ... " as shown in the book gives an eval error message. To get rid of the message I need to put the code in braces.

b) even putting the code in braces it does not work. See this:

$kards = -99; $bad="AKQJT98765432KKKK"; $card='K'; eval {print "eval is testing for $card in $bad \n"; $kards = $bad =~ tr/$card//; # tried also tr/$card/$card/; +same bad result. }; print "Num of $card in $bad = $kards \n"; $king = $bad =~ tr/K//; print "Num of K in $bad is = $king \n"; =cut Returns the following: eval is testing for K in AKQJT98765432KKKK Num of K in AKQJT98765432KKKK = 0 Num of K in AKQJT98765432KKKK is = 5 =end

Why isn't the variable $kards set to 5 as it should be?

UPDATE

The problem is that $kards should be set to the result of the eval execution, not put inside the eval execution. So this works:

$kards = -99; $bad="AKQJT98765432KKKK"; $card='K'; # this next bit works. xtr is just so I can print what eval is eva +l ing. $xtr = "$bad =~ tr/$card//;" ; $kards = eval "$bad =~ tr/$card//;"; #<====== Note the ; " ; at + the end. This seems required. print "xtr kards = $kards from eval cmd : $xtr\n"; #output: xtr kards = 5 from eval cmd : AKQJT98765432KKKK =~ tr/K// +;

but note that the code eval runs must be in double quotes, not in braces. so <code>$kards = eval {$xtr;} ; does NOT work.

Replies are listed 'Best First'.
Re: tr operator in eval -- updated
by haukex (Archbishop) on Sep 26, 2020 at 08:37 UTC
    The example on p.76 of the camel book

    What edition of the book are you looking at? In the fourth edition, the example that I think you're referring to is on page 191, and in the third edition, the page number is similarly high. From the 4th ed, it's:

    $count = eval "tr/$oldlist/$newlist/"; die if $@; # propagates exception from illegal eval contents
    a) eval " ... code ... " as shown in the book gives an eval error message. and eval "tr/$oldlist/$newlist/;" This does not work.

    No, there is nothing wrong with that example code in the second, third, and fourth editions of the book. (Nitpicks: under strict, the variables obviously need to be predeclared, and the if $@ pattern is not recommended.)

    This means that the code you're running is not exactly the code from the book, and you haven't shown an example of the code that does produce the error you're referring to, nor the error message itself. How do I post a question effectively? and I know what I mean. Why don't you?

    even putting the code in braces it does not work.

    There is a significant difference between eval STRING and eval BLOCK. The latter is basically just an exception trapping mechanism, it does not delay the compilation of the code inside the block until runtime, which is what the former does, and it is required in this case, since as the book explains, the pattern is built at compile time.

    The problem is that $kards should be set to the result of the eval execution, not put inside the eval execution.

    Well, that at least gives a hint as to what might have been going wrong. So you probably wrote eval "$kards = $bad =~ tr/$card//;"? Note that what eval STRING does is compile a string of Perl code and execute it. If you take a close look at the string you're giving it, "$kards = $bad =~ tr/$card//;", note that since it's in double quotes, all the variables inside are interpolated. In other words, using the values you've shown here, the string you're actually handing to eval to compile and run is '-99 = AKQJT98765432KKKK =~ tr/K//;', which of course doesn't make sense. You meant for eval to execute the Perl code '$kards = $bad =~ tr/K//;', i.e. only the value of $card should be interpolated, which means you need to prevent the other two variables from being interpolated, for example by putting backslashes in front of the sigils, as in "\$kards = \$bad =~ tr/$card//;".

    This also means that your second example code, $kards = eval "$bad =~ tr/$card//;";, has the same issue, because you're asking Perl to execute the piece of code 'AKQJT98765432KKKK =~ tr/K//;'. This only happens to work if you're not using strict, because Perl is taking AKQJT98765432KKKK as a "bareword", a feature that is not allowed under strict "subs". And since one shoud always Use strict and warnings, you should escape $bad in this code as well.

      Thank you very much for the detailed explanation haukex. It is very complete. To answer some of your other questions I am referring to 2nd Edition (1999) of Camel book. The example on p.76 is in the explanation of tr, not in the explanation of eval. the literal text from that page is :  eval "tr/$oldlist/$newlist/"; die $@ if $@; I knew that perl had progressed some since 1999 but it did not occur to me that there would of course be further editions of the Camel book. Thanks again. pgmer6809
Re: tr operator in eval -- updated
by tobyink (Canon) on Sep 26, 2020 at 09:42 UTC

    #<====== Note the ; " ; at the end. This seems required.

    No, only the second semicolon is required. And why wouldn't it be? Statements generally need semicolons to separate them from the next statement, apart from block-like statements like if ($cond} { BLOCK } which don't need a semicolon after.

    The difference between the two eval uses can be summed up like this:

    use strict; use warnings; use feature 'say'; my $var = "Hello"; eval " say '$var' "; eval { say '$var' };

    If you understand why those two have different results, you will understand why stringy eval is working for you and block eval is not.

    Understanding why this works but you couldn't do it with the block form of eval might also help:

    use strict; use warnings; my $var = q(Hello',"\n"); eval "print '$var";
Re: tr operator in eval -- updated
by AnomalousMonk (Archbishop) on Sep 26, 2020 at 16:43 UTC

    When using an eval STRING; statement, I find it very helpful to build the string separately so that it can be examined with the Perl debugger (see perldebug) or even with a simple print point.

    Win8 Strawberry 5.30.3.1 (64) Sat 09/26/2020 11:37:04 C:\@Work\Perl\monks >perl use strict; use warnings; my $kards; my $bad = 'AKQJT98765432KKKK'; my $card = 'K'; my $e_string = "$kards = $bad =~ tr/$card//"; # print "e_string: >$e_string< \n"; # for debug eval $e_string; print "1: Num of '$card' in $bad is == $kards \n"; my $kings = $bad =~ tr/K//; print "2: Num of '$card' in $bad is == $kings \n"; __END__ Use of uninitialized value $kards in concatenation (.) or string at - +line 8. Use of uninitialized value $kards in concatenation (.) or string at - +line 12. 1: Num of 'K' in AKQJT98765432KKKK is == 2: Num of 'K' in AKQJT98765432KKKK is == 5
    Uncomment the print point and the problem with the eval becomes obvious. (Enabling warnings helps too!)

    On another topic...

    You seem to have been going over code examples as a self-teaching exercise. That's commendable, but it should be pointed out that in practice, there are arguably better ways of handling the "count the occurrences of an arbitrary set of characters in a string" problem. eval is expensive, and it's better to avoid the guillotine when a scalpel will do. A couple of examples (of, I'm sure, many).

    Win8 Strawberry 5.30.3.1 (64) Sat 09/26/2020 12:14:28 C:\@Work\Perl\monks >perl use strict; use warnings; my $n_found; my $string = 'AKQJT98765432KKKK'; my $set = 'AQK'; # s/// probably fastest in practice. my $destroy = $string; # s/// alters its target $n_found = $destroy =~ s{ [\Q$set\E] }{}xmsg; print "s///: Num of '$set' in '$string' == $n_found \n"; # m// probably a bit slower, use of =()= a bit tricky. $n_found =()= $string =~ m{ [\Q$set\E] }xmsg; print "m//: Num of '$set' in '$string' == $n_found \n"; # tr/// is fastest, but needs expensive eval for arbitrary sets. $n_found = $string =~ tr/KAQ//; print "tr///: Num of 'KAQ' in '$string' == $n_found \n"; __END__ s///: Num of 'AQK' in 'AKQJT98765432KKKK' == 7 m//: Num of 'AQK' in 'AKQJT98765432KKKK' == 7 tr///: Num of 'KAQ' in 'AKQJT98765432KKKK' == 7
    This code also works under Perl version 5.8.9.


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

      Thanks very much. There is a lot I don't know about perl, and even more so about 'modern' perl since I only dive into programming about once per year. I had in fact discovered the s/char//g trick after making the OP. Those 'set' operations look very useful. I will have to study up on them. In this case I am not (yet) sure they will work, since a suit like "AKQxxx" is valid but one like "AAKxxx" is not. (There is only one Ace of spades for example.) So assuming counting members of the set AKQ would return 3 in both cases(??), but the second is invalid. But no doubt I will find a use for set operations now that I know they are available. pgmer6809 PS. I have 'discovered' (I am sure I am not the first!) that a way to avoid destruction of the original string is to use $& as in $deal =~ s{AKQJ}{$&}xg; will count the number of top cards in $deal, but leave it unchanged: </code> $deal="AQTxx"; $c = $deal =~ s{AKQJ}{$&}xg; print "$c,$deal\n"; output: 2,AQTxx
        Those 'set' operations ...

        Please note that the term "set" is used very loosely in this post. :)

        ... a suit like "AKQxxx" is valid but one like "AAKxxx" is not. (There is only one Ace of spades ....)

        This seems like a two-phase problem. Once you verify that a string is in a standard format with, say, no character occuring twice, it may be very easy to count characters unambiguously.

        Within reasonable limits, a single regex could be very handy for verifying that a string:

        • has min to max characters from a given set of characters;
        • that the characters in the string are only from the set;
        • that each character from the set occurs only once in the string; and
        • that the characters in the string are in a required order.

        It may be enough just to verify that no character occurs more than once in a string. For that,
            length $string == uniq split '', $string
        or
            $string !~ m{ (.) (?= .*? \1) }xms
        does the trick.

        And there are many other conceivable approaches, all depending on your precise requirements.

        Update: Added another simple no-repeat test.


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

Re: tr operator in eval
by AnomalousMonk (Archbishop) on Sep 26, 2020 at 03:15 UTC

    tr does not interpolate.

    Win8 Strawberry 5.30.3.1 (64) Fri 09/25/2020 23:09:19 C:\@Work\Perl\monks >perl my $kards = -99; my $bad='$card$card'; my $card='K'; eval {print "eval is testing for $card in $bad \n"; $kards = $bad =~ tr/$card//; # tried also tr/$card/$card/; same + bad result. }; print "1: Num of $card in $bad = $kards \n"; $king = $bad =~ tr/$card//; print "2: Num of K in $bad is = $king \n"; __END__ eval is testing for K in $card$card 1: Num of K in $card$card = 10 2: Num of K in $card$card is = 10
    Here, tr is counting the number of characters in the '$cards' literal character set that are in the $bad string.


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

Re: tr operator in eval -- updated
by tybalt89 (Monsignor) on Sep 26, 2020 at 07:49 UTC
    #!/usr/bin/perl use strict; # https://perlmonks.org/?node_id=11122227 use warnings; my $kards = -99; my $bad="AKQJT98765432KKKK"; my $card='K'; # this next bit works. xtr is just so I can print what eval is eval in +g. my $xtr = "$bad =~ tr/$card//;" ; $kards = eval "\$bad =~ tr/$card//"; # missing \ , no ; needed inside +eval print "xtr kards = $kards from eval cmd : $xtr\n";
Re: tr operator in eval -- updated
by BillKSmith (Monsignor) on Sep 26, 2020 at 17:04 UTC
    The documentation for tr clearly states that it does not interpolate. As a workaround, we can interpolate into a string and then evaluate that string. The first two tests below demonstrate that we build the expected string and that it evaluates correctly. As a notational convenience, we can combine these two steps into one statement. The third test below demonstrates that form.
    use strict; use warnings; use Test::Simple tests=>3; my $kards; my $bad = 'AKQJT98765432KKKK'; my $card = 'K'; my $string = "\$bad =~ tr/$card//"; ok($string eq '$bad =~ tr/K//', "string = '$string'"); $kards = eval $string; ok($kards == 5, "Number of K's in $bad is $kards"); $kards = eval "\$bad =~ tr/$card//"; ok($kards == 5, "Combined form");

    OUTPUT:

    1..3 ok 1 - string = '$bad =~ tr/K//' ok 2 - Number of K's in AKQJT98765432KKKK is 5 ok 3 - Combined form
    Bill
      Thanks Bill.

      I was aware that tr did not interpolate, but the (very old) edition of Camel book said I could achieve the effect I wanted with eval.

      The example they gave omitted a few key characters, and a more complete statement. I missed the necessity of having the backslash escape before the source string and also having ; characters to terminate both the expression to be eval'ed and also the eval statement itself.

      Thanks to the various posts here I am now more enlightened. Appreciated your use of Test:: in your post. I need to make better use of that package.

      pgmer6809
        I missed the necessity of ... having ; characters to terminate both the expression to be eval'ed and also the eval statement itself.

        A ; (semicolon) is not necessary to terminate the last statement in a file, block, or an eval string derived from an expression; if any of these have only a single statement, no semicolon termination at all is necessary. The eval statement itself must be ;-terminated because other statements follow it.

        Update: Another, more general and perhaps better way to put it is that every statement must be terminated, either by a semicolon or by the end of the block, file or eval string containing it. And, of course, extra semicolons have no effect.


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

        Try removing the first backslash from my post and run it again. Study the output. It should be very clear that the first two tests are "not ok". Could you have found the problem yourself if you had seen this before you posted? After the first test fails, it should be clear that the second test does not stand a chance because it is evaluating the wrong string. Look at the string in the output. Note that both variables are interpolated. If you could not solve the problem at this point you could at least have asked the right question: "How can I interpolate one variable in a string, but not the other?" (The answer of course is to escape the dollar sign so that it will be treated as a literal character, not as the sigil of a variable.)
        Bill
Re: tr operator in eval -- updated
by LanX (Saint) on Sep 26, 2020 at 08:11 UTC
    You seem to be confusing eval BLOCK and eval EXPR. They do very different things.

    see eval

    Cheers Rolf
    (addicted to the Perl Programming Language :)
    Wikisyntax for the Monastery

Re: tr operator in eval
by pgmer6809 (Sexton) on Sep 26, 2020 at 03:54 UTC
    I don't think that answer is correct. I know that tr does not interpolate, the patterns are defined at compile time according to the camel book. But the book says that you can put variables in the tr statement if you then use eval to execute the statement. the example in the book (p.76) is  eval "tr/$oldlist/$newlist/;" This does not work. this two step however does work:
    # this next two step works..... $xtr = "$bad =~ tr/$card//;" ; $kards = eval "$xtr;"; print "xtr kards = $kards from eval cmd : $xtr\n"; #output: xtr kards = 5 from eval cmd : AKQJT98765432KKKK =~ tr/K// +;

      Try this:

      Win8 Strawberry 5.30.3.1 (64) Sat 09/26/2020 0:05:59 C:\@Work\Perl\monks >perl use strict; use warnings; my $kards; my $bad = 'AKQJT98765432KKKK'; my $card = 'K'; eval "\$kards = \$bad =~ tr/$card//"; print "1: Num of '$card' in $bad is == $kards \n"; my $kings = $bad =~ tr/K//; print "2: Num of '$card' in $bad is == $kings \n"; __END__ 1: Num of 'K' in AKQJT98765432KKKK is == 5 2: Num of 'K' in AKQJT98765432KKKK is == 5
      Note that  \$kards \$bad in the eval string are escaped (update: and $card is not). Why?

      Update: Just read your update to the OP and I think you pretty much have the idea I was trying to get across. However, now try running the OP update code with strict and warnings enabled.


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