in reply to built-ins sometimes iterating over a list, sometimes not

exists on a slice, I never thought of trying it, but it certainly sounds like a nice feature request. (No idea how easy it is though, but you can always ask.)

But the hideous line is worse than needed. Just do this:

if (grep !exists $args->{$_} @mandatory_args) { printUsage() and exit; }
which looks much better. No need to map and then grep, just grep!

Now for a few stylistic points.

  1. I don't like nesting logical assertions to save lines. Combining && and if used for program flow on one line is hard on maintainance programmers, break it up.
  2. Of the various ways of calling a function, the only one to avoid is &foo. Unless you need the implicit parameter passing, avoid it. You don't want someone picking up the habit without knowing about the gotcha.
  3. End of line indentation is the only kind of indentation I object to. As pointed out in Code Complete, it results in very rapidly getting deep nesting, it breaks the easily seen connection between depth of indentation and depth of logic, and it represents a stylistic maintainance problem; you can't edit the code without unnecessary playing around with indentation on following lines.
  4. I returned to using an array of mandatory arguments in the test in the hope that you could reuse that array in the usage statement. If possible, it is nice to have your usage statement automatically synchronized with your test for correct usage. The rest of the details, in fine mathematical tradition*, are left as an exercise.
BTW if you can figure out a good way to handle the last point, I am all ears. This is a variant on the problem I addressed at Re (tilly) 2: passing subroutine arguments directly into a hash. The solution I outline there works, but the messages are inappropriate for a user interface, rather it is an internal interface to help programmers debug.

* According to tradition, when mathematicians come to a point they need in a text, whose proof they can't remember, they make it into an exercise for the reader. :-)

Replies are listed 'Best First'.
Re: Re (tilly) 1: built-ins sometimes iterating over a list, sometimes not
by dragonchild (Archbishop) on Nov 08, 2001 at 19:38 UTC
    Ok. So, I'm stupid about using the extra map. I'm also stupid about using the wrong invocation of the function. I'm so used to method calls which don't need the & or the () that I forgot which is supposed to be used when calling a 'normal' function. :-)

    I find that nesting logical assertions helps the reader understand what a string of maps and greps and sorts is doing. Yes, in theory, the programmer is supposed to use intermediate variables, but no other language has the same piping that Perl has, so Code Complete never addressed the issue. I've seen merlyn and theDamian both use a similar form to do the Orcish manauver or Schwartzian transforms. *shrugs*

    As for mandatory args ... *sighs* This is similar to another problem I have that I posted somewhere, but don't have the motivation to find.

    *ponders* Wouldn't something like the following work?

    sub printUsage { my ($mandatory, $optional) = @_; print "Usage: $0\n"; print "-$_ ", uc $_, " " for @$mandatory; print $/; print "[-$_ ", uc $_, "]" for @$optional; print $/; }

    Of course, the formatting isn't as good. I'll leave that as an exercise for the reader. *grins*

    Update: I changed how I did the calls into Getopt::Long. Please critique.

    my @mandatory = qw(user to_db to_sid to_site); my @optional = qw(password vendor type model); my @default = qw(from_db from_sid); our $from_db = "some_db"; our $from_sid = "some_sid"; my $args = {}; my $rc; { no strict 'refs'; $rc = GetOptions($args, (map { "${_}=s" } @mandatory), (map { "${_}:s" } @optional), (map { ("${_}=s", \${"$_"}) } @default), ); } printUsage(\@mandatory, \@optional, \@default) unless $rc; printUsage(\@mandatory, \@optional, \@default) if grep !exists $args->{$_}, @mandatory;

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      Actually Code Complete does address this exact issue. I don't have it in front of you so I can't give page numbers, but they talk about the importance of not reducing lines of code by putting more on one line. I think it was talked about with particular reference to C's trinary operator.

      As for the question of whether nesting logic helps or hurts, my experience is very strongly that it hurts my comprehension. What I find best for handling nested maps, greps, and sorts is to put them on separate lines, indented, to be read from bottom to top. That is the same strategy with any complex control logic. If it is a single "thought" in your program, make it into line-noise. If it is central to how your program works, break it visually along the lines of how it is to be understood.

      I have seen similar styles from other programmers. Including both merlyn and TheDamian. Again, I sometimes break out logic, sometimes don't. But if it is central to what I am doing, I break it out. Likewise if I start getting multiple control statements on the same line and want to reach for && as a control statement, I break it out.

      As for the question of whether other languages have the piping that Perl does, I disagree. Here is a Schwartzian sort of a pipe-delimited list on the 3'rd then 5'th columns (ascending then descending) in Perl:

      my @sorted = map {join, "|", @$_} sort {$a->[2] cmp $b->[2] or $a->[4] cmp $b->[4]} map {[split /\|/, $_, -1]} @my_list;
      Here is how you do the same thing in Ruby:
      sorted = my_list.map { |i| [ i.split(/\|/)[2,4], i ] }.sort.map { |i| i[-1] }
      Do you see? Chaining method calls achieves exactly the same effect as Perl's pipelining does, and it even has the benefit of reading more naturally left to right! So the formatting issue is hardly unique to Perl...
        What I find best for handling nested maps, greps, and sorts is to put them on separate lines, indented, to be read from bottom to top.

        I do this as well, my only issue with it is where to put the listor array, on the same line as the last transformation or the line below? Also I can never quite decide how to deal with 'blockless' transforms such as sort.

        @list=map {substr($_,1)} sort map {pack("CA*",length($_),$_)} @words; #or @list=map {substr($_,1)} sort map {pack("CA*",length($_),$_)} @words;
        About your example from Ruby, are you allowed arbitray whitespace in between the transforms? ie could you write that as the following?
        sorted = my_list.map {|i| [ i.split(/\|/)[2,4],i ]}. sort. map { |i| i[-1] }
        ...even has the benefit of reading more naturally left to right!

        Well, while it wouldnt be difficult to write a class that supported this type of syntax, I have to say that i'm not so sure that this is more natural or not. In fact, while I dont think it would be that hard to get used to or even that it would bother me, I would actually argue that it is less natural than the perl notation, which I hasten to add does seem a little odd at first, seemingly going against the convention in a program that lines are executed in order from top to bottom. This I believe is because it is easy to think in terms of statement execution rather than what is really happening, list assignment, or function chaining.

        For the sake of clarity in my argument Id like to point out that normally values move from the right to left in statements. $x=$y; for instance involves evaluating the right side and placing its value in the container on the left side. With lists we extend the idea to that of 'pouring' values from the right side into the left side.

        Now when I see  @greets=map{"Hello $_!"}@names; the idea is extended so each value from the list at the right goes left, into the block and then left again into the array. And so on when we have longer chains. This also matches the way it would be written if these were all functions.

        @list=map(substr($_,1),sort(map(pack("CA*",length($_),$_),@words)));
        Whereas the method form would translate into a multistatement equivelent
        my @tmp = map { pack("CA*",length($_),$_) } @words; my @tmp2= sort @tmp; my @list= map { substr($_,1)} @tmp2;
        Which makes sense when written as multiple statements, but when written as one statement as you showed in ruby it actually seems to be unnatural. The result gets taken from the extreme right hand side and stuck in the container on the left side, which hardly seems more natural than the values flowing consistantly from right to left.

        Just my $0.02 :-)

        Yves / DeMerphq
        --
        Have you registered your Name Space?