Heh. Good catch. And to be honest I posted the update before i read your reply... :-) And yours is more idomatic. Cool. :-)
--- demerphq
my friends call me, usually because I'm late....
| [reply] |
Thank's for the response. It works; of course!, but why?
I guess I'm what you might call an intermediate programmer
and I like to understand what's going on; so let me ask a
couple of questions.
- I've never seen "quotemeta(shift)".
I use local($from,$to,$opts) = @_;
- What is ($opts=shift || "")=~tr/cdsCDS/cdscds/d; doing?
- Help me understand how this works
my $tr=eval "sub { (defined \$_[0] ? \$_[0] : $_)
=~tr/$from/$to/$opts}";
- What happens if the ( die "$@" if $@ ) is simply left out
and the eval fails.
| [reply] [d/l] [select] |
use Carp;
sub make_tr {
my $from=quotemeta(shift);
my $to =quotemeta(shift);
my $opts=shift || "";
Carp::croak "Bad option in '$opts'" if $opts!~/[cds]/;
my $eval_str="sub { (@_ ? \$_[0] : \$_)=~tr/$from/$to/$opts}";
my $tr=eval $eval_str;
die "While evaling\n$eval_str\n$@" if $@;
return $tr;
}
-
So what does the quotemeta(shift) do? Well, shift removes the first value from an array and returns it. If it isnt provided a specific array to operate on then it uses @_ or @ARGV, depending on whether it is called inside a subroutine or not. Quotemeta is a handy little function for turning strings with weird (meta) characters in them (anything that isnt a word character I think) into a backslashed version. We use it because if the symbols you wanted to translate were the same as used in the tr/// function (ie '/') then it would break the code, so once quotemeta'ed something like "a/b" would become "a\/b" and not break the code.
As for the local() bit, I think the safest thing for me to say is that you should read up on my() and local() in perlfunc. They arent the same thing, and if you are getting into the habit of using local like that then you need to get out of it pretty fast. About %99.999 of the time beginners use local() they really wanted my(). Have a read of Coping With Scoping for an indepth review of the issues.
-
Ok the code you mention has been replaced with what I consider to be a smarter solution. The original idea was to delete any characters that arent allowed to be options for a tr///, as well as to lowercase CDS as a convienience. Apon reflection I think this was a bad idea as the calling programmer might not realize that this had happened and wonder why such weird things were happening. The replacement simply throws an exception if an illegal character were passed in. I think this is more useful and intuitive than my first approach. And if your question also refered to the shift || "" then it means shift a value off of @_, if that value is in any way false (ie "", 0 or undef) then replace it with "" which is a perl idiom for converting undef values to something harmless (warningless actually) like "" (or to a default value). Normally (until perl 5.10 introduces the // operator and its cousin =//) you have to be careful about this idiom because there are three different forms of false and most often we are only concerned with undef and dont want to change the others. However in this case '0' is a totally illegal value that we can more or less safely silently convert, and "" would convert to itself, so we dont have a problem.
-
my $tr=eval "sub { (defined \$_[0] ? \$_[0] : $_)=~tr/$from/$to/$opts}";
Ok, i can see why this one threw you for a loop. (I simplified it in the above code.) Basically it generates an anoymous subroutine to do the desired tr/// as it is assumed that you will want to do a given tr/// more than once. In more detail the first thing to understand is that the regex binding operators =~ and !~ apply to any lvalue. They dont just apply to scalars/strings. So what the (defined $_[0] ? $_[0] : $_) or the (@_ ? $_[0] : $_) do is to check to see whether an argument has been passed to the subroutine. (Its called the ternary operator, its like a funny looking if statement that returns a value. Syntax: CONDITION ? TRURETURN : FALSERETURN ) If one has been passed then it is used (via $_[0]) for the tr/// and if one hasnt then $_ is used. Since both of these values are lvalues we can bind this entire thing to the regex. (it is precisely for this kind of thing that we have the ternary operator, we could do the same thing with an if, but code would be duplicated.) Its important to understand that the actual elements of the @_ array are special. They are aliases to the passed variable, not copies or references. (Well, actually an alias is like a funny kind of reference that doesnt need to be dereferenced.) So that part determines which variable the tr/// affects. The tr/// part is pretty straight forward.
Now why is $_[0] backslashed and the $from is not? Well, because we using string interpolation to build up a piece of code that defines a subroutine that we then return. The $_[0] needs to be literally in the code, but we want the $from, $to and $opts to be trnsformed into their contents, the leading \ inside a string keeps a variable from being interpolated. (This is why I changed the error statement from the eval so that you can see the actual code that caused the error)
- Well, I wouldnt do that. What would happen in an error situation is that no new subroutine would be generated and when you tried to do &$tr or $tr->(); an exception would be thrown as the value $tr would be undefined.
May i suggest you get yourself a username and come hang out. It sounds like theres stuff you could learn....
HTH
--- demerphq
my friends call me, usually because I'm late....
| [reply] [d/l] [select] |