For all those just tuning in, Ben and I are in violent and vocal agreement with each other, and at this point are merely quibbling over semantics :)
"Ben Scott" <[EMAIL PROTECTED]> writes: > Er, yes. "blah" in this case was meta-syntactic, and I was still > thinking of the first example in this discussion, which had LTS > (Leaning Toothpick Syndrome). I will use // if the regexp doesn't > suffer from LTS. I use m{} or s{}{} when the regexp otherwise > contains slashes. Something about the use {} and () in regexps really bothers me. I think it's because in general, perl overloads too many things to begin with. To use {} for regexp delimiting is confusing and completely non-intuitive to me. They are meant to denote either a hash element or a code block. Trying to make my mind use them for regexps hurts :) To avoid LTS and backslashitis in a regexp, I tend to do something like: m|/foo/bar|/bar/baz|g; The | is close enough to / that it's instantly clear to me. > "A foolish consistency is the hobgoblin of little minds." -- Ralph > Waldo Emerson Yeah, what the old, dead guy said :) > As a completely non-contrived example, here is an illustration of > when I think implicit use of $_ is very appropriate. [...] > sub condense_type($) { > # condense a MIME content type to something shorter, for people > $_ = $_[0]; > s{^text/plain$} {text}; > s{^text/html$} {html}; > s{^text/css$} {css}; > s{^text/javascript$} {jscript}; > s{^text/xml$} {xml}; > s{^text/} {}; > s{^image/.*} {image}; > s{^video/.*} {video}; > s{^audio/.*} {audio}; > s{^multipart/byteranges}{bytes}; > s{^application/} {}; > s{^octet-stream$} {binary}; > s{^x-javascript$} {jscript}; > s{^x-shockwave-flash$} {flash}; > s{\*/\*} {stars}; # some content gets marked */* > return $_; > } > > I could have used a regular named variable (say, $type) and > repeated "$type =~" over and over again for 14 lines. I believe > that would actually harm the readability of the code. Agreed, though, we (by we, I mean the company which currently puts food on my table :) do things like this slightly differently, completely avoiding the $_ dilemma: $match = shift; %mimeTypes = ('^text/plain$' => "text" , '^text/html$' => "html" , '^text/css$' => "css" , '^text/javascript$' => "jscript", '^text/xml$' => "xml" , '^text/' => "" , '^image/.*' => "image" , '^video/.*' => "video" , '^audio/.*' => "audio" , '^multipart/byteranges' => "bytes" , '^application/' => "" , '^octet-stream$' => "binary" , '^x-javascript$' => "jscript", '^x-shockwave-flash$' => "flash" , '*/*' => "stars" ,# some content gets marked */* ); foreach my $mtype (keys %mimeTypes) { if ($mtype =~ /$match/) return $mimeType{$mtype}; } } Also, the foreach could be written as: map { ( $match =~ /$_/) && return $mimeTypes{$_}} keys %mimeTypes Though I find this completely readable, it suffers from the problem that it's not easily extensible. If you decide you need to do more processing within the loop, the foreach is much easier to extend. You just plonk another line in there and operate on the already existing variables. With the map() style loop, this becomes more difficult. So, though I love map(), I would have to argue this is not the best place to use it. Once readability has been achieved, the next priority ought to be future maintenance and extensibility, IMO. > As a counter-example from the same script, here's something using > explicit names and grouping which isn't strictly needed, because I > find it clearer: > > sub condense_size($) { > # consense a byte-count into K/M/G > my $size = $_[0]; > if ($size > $gigabyte) { $size = ($size / $gigabyte) . "G"; } > elsif ($size > $megabyte) { $size = ($size / $megabyte) . "M"; } > elsif ($size > $kilobyte) { $size = ($size / $kilobyte) . "K"; } > return $size; > } I tend to like this style too, though I'd use a slightly different syntax. It's otherwise exactly the same. my $size = shift; ($size > $gigabyte) && { return (($size/$gigabyte) . "G")}; ($size > $megabyte) && { return (($size/$megabyte) . "M")}; ($size > $kilobyte) && { return (($size/$kilobyte) . "K")}; Or, perhaps, if you wanted to be a little more cleverer: my %units = ($gigabyte => sub { int($_[0]/$gigabyte) . 'G'}, $megabyte => sub { int($_[0]/$megabyte) . 'M'}, $kilobyte => sub { int($_[0]/$kilobyte) . 'K'}, ); foreach my $base (sort {$b <=> $a } keys %units) { if ($size > $base) { print ($units{$base}->($size),"\n"); last; } } This last approach is both too clever by 1, but also, slightly easier to maintain, given that to add another size, you add one line. You can even add the one line anywhere you want in the hash. Since the keys are integers, they'll sort correctly in the foreach loop. If you wanted to be *really* clever(-2 points!), you could probably do this: my %units = ($gigabyte => sub { int($_[0]/$_[1]) . 'G'}, $megabyte => sub { int($_[0]/$_[1]) . 'M'}, $kilobyte => sub { int($_[0]/$_[1]) . 'K'}, ); and call into the hash like this: $units{$base}->($size, $base),"\n"); But now that I've done that, it ought to be obvious that we can easily factor out the division so it only happens once: my %units = ($gigabyte => 'G', $tera => 'T', $megabyte => 'M', $kilobyte => 'K', ); foreach my $base (sort {$b <=> $a } keys %units) { if ($size > $base) { print (int($size/$base),$units{$base},"\n"); last; } } This last form has all the advantages of readability, extensibility, and ease of maintenance, without any of the repetition. >> Right, which is why you shouldn't depend upon $_ in these contexts and >> explicitly state a variable name ... > > A named variable would be *two* more things. ;-) Not if done correctly, as I did above :) (at least IMO) >> my $file = shift; > > You're using an implicit argument to shift there. ;-) Yeah, I am. We don't actually do that here at work. We do things like this: my ($file) = assertNumArgs(@_, 1); Which enforces that we only pass in 1 argument if that's all we're expecting. assertNumArgs() compares the number passed to it with the number of elements in @_, and croaks() if they don't match. It's very nice error checking. Alas, assertNumArgs() is a part of a huge home-grown library of routines we have. >> The compelling argument is this: It should be blatantly obvious to >> whomever is going to be maintaining your code in 6 months what you >> were thinking > > I do not think I could agree with you more here. The thing you seem > to be ignoring in my argument is that "clarity" is subjective and > often depends on context. :) I'm not ignoring it. I'm saying that where you have stopped because you think it is sufficiently clear can in fact be made cleaner and clearer for the sakes of both clarity and future maintenance. I'm assuming/guessing that you're coming at this argument from the point of view of "I'm going to write a simple script to do something not overly complex, so why waste a lot of time with it, it's good enough". I'm coming at it from a software development context where whatever is being written is ultimately going to be part of a much larger whole. We have litterally hundreds of thousands of lines of perl code which has evolved over a 7+ year period. Any number of people have had their hands in this code. If strict coding standards and practices had not been adhered to, it would be nigh impossible to maintain, never mind further development of. As it is, I, who am *NOT* a software developer by training (or any other definition :) and who is nothing more than self-taught in perl, have been able to jump into this code and contribute quite a bit to it. I have both furthered code written by others, and others have extended/debugged mine. > I'm not going to penalize the competent because there are others who > are incompetent. You don't have to. I don't think any of the code I wrote above penalizes the competent. If anything, it's nice, clean code written without any assumptions other that of some (very little) competence of the reader. Other than the intermediary steps where I was using anonymous sub routines as the value of a hash element, which is arguably an advanced programming technique (not at all isolated to perl), there was nothing very complicated about the code I wrote. Especially the last version which used nothing more than an extremely simple hash and a normal foreach loop. In my opinion, that last version is both elegant and easy to read, maintain, debug. > Let me restate: A pattern which is powerful and easy-to-use is > sometimes unavoidably non-obvious. Agreed. But it can be written such that it appears obvious. Was my code not both powerful and easy-to-use? I'll concede that it might not be obvious, but when you look at it, is it not obvious what I'm doing ? > Or perhaps an example of a similar principle in a different context: > When invoking tar from a shell script, which of the following do you > prefer? > > tar --create --gzip --verbose --preserve-permissions > --file=/path/to/file.tar.gz /etc > > tar czvpf /path/to/file.tar.gz /etc Neither: tar='/usr/bin/tar' tarOptions='<some list of options in any way that makes sense>' archivePath='/etc' outputPath='/path/to/file.tar' $tar $tarOptions $outputPath $archivePath Yes, I know I'm being a p.i.t.a. here :) But I also think that there's a slight difference between the long/short options for a command line utility and the nuances of perl written anyway you feel like it at the moment. There are only a finite number of options for any given command. The same is not true for writing perl code. Either of the above ways you used tar is perfectly fine, IMO, since the tar man page is fairly short and concise compared to learning a programming language. Though, I will note, that if you look at the process table of an AMANDA client using tar, you will see that they opted to use all long option names. Why? Ease of understanding when debugging, or, what I refer to as the 3:00am test. If I need to debug that shell script above (or, as has been more recently the case, amanda taking too long), it saves me a *huge* amount of time to to be able to see in the ps output that tar is using --preserve-permissions or --listed-incremental vs. having to resort to reading the man page to re-remember what the -p or -g means, especially if the args are not in a sane, logical order, eg. cpgfz, which not be overly intuitive at 3:00 in morning when that "simple script that can't possibly have a bug in it" goes kerflooey. Which you rather have: tar lScCgf - /u1 /var/lib/amanda/gnutar-lists/rc_u1_0.new or tar --create --file - --directory /u1 --one-file-system \ --listed-incremental /var/lib/amanda/gnutar-lists/rc_u1_0.new --sparse --ignore-failed-read --totals . I was fortunate that the authors of AMANDA chose the latter. I consider myself competant, and not the least penalized by their choice; rather, thankful for it. I now return you to your regularly scheduled off-topic conversation :) -- Seeya, Paul _______________________________________________ gnhlug-discuss mailing list gnhlug-discuss@mail.gnhlug.org http://mail.gnhlug.org/mailman/listinfo/gnhlug-discuss/