Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Re: crafting a decent CLI

by Abigail (Deacon)
on Jul 12, 2001 at 02:24 UTC ( [id://95878]=note: print w/replies, xml ) Need Help??


in reply to crafting a decent CLI

Your question was already answered, but I would like to suggest an alternative way to do your main loop:
{ local $_; print "> "; chomp ($_ = <STDIN>); redo unless /\S/; next if "FALSE" eq cmd_select split ' '; }
(If you don't like a bare block, you could put a while (1) in front of it and change the next if to a last unless).

My version of the loop doesn't use goto to break out of the loop and re-enter it, it doesn't use an explicite variable $exit, which is compared to the true value "FALSE", it uses chomp in favour of chop and it will try to execute the command 0 which your version skips, but it won't try to execute a line consisting of a space like yours. I also have eliminated to need for the variables $key and @param. I would prefer cmd_select to return a true/false value instead of "FALSE" and something else.

-- Abigail

Replies are listed 'Best First'.
(tye)Re: crafting a decent CLI
by tye (Sage) on Jul 12, 2001 at 13:32 UTC
    My version of the loop doesn't use goto

    No, it uses redo and next, which can be worse than goto. q-: (Except perhaps from the perspective of the optimizer which doesn't bother to try to figure out goto most of the time.)

    But what I'm really puzzled about is this new "naked block with lots of redo, next, and last" that I've been seeing lately. This example doesn't require anything so powerful and easy-to-abuse as that. Why not some simple loops like:

    local $_; do { do { print "> "; chomp( $_= <STDIN> ); } until( /\S/ ); } until( 'FALSE' eq cmd_select( split ' ' ) );
    or
    local $_; do { print "> "; chomp( $_= <STDIN> ); } until( /\S/ && 'FALSE' eq cmd_select( split ' ' ) );
    or even have your code handle EOF on STDIN:
    local $_; do { do { print "> "; defined( $_= <STDIN> ) or die "EOF on STDIN"; chomp( $_ ); } until( /\S/ ); } until( 'FALSE' eq cmd_select( split ' ' ) );
    (if you need to clean up on EOF, then throw this in a sub and return instead of die)

    That way your code actually looks like "loop reading commands until you get a non-empty one and loop doing that until a 'quit' is requested". Not, "here is a block that I may or may not be using as a loop (please parse the rest of the block if you want to know), read a command, goto the top of the nearest surrounding loop (or naked block but not a conditional or do block) if the command is empty, otherwise go to the continue block (if there is one) or the top of the nearest surrounding loopish block, if a 'quit' was not requested". (:

    I know that something like the following is a fairly common task:

    lots of work to get next thing; # possibly giving up for any of several reasons goto TOP if thing isn't interesting; if( thing is invalid ) { instruct; goto TOP; } do lots of stuff with thing; goto TOP if not last thing; if( no valid things found ) { complain; return; } finish up; return result;
    and that you can code that with a naked block and a few redos and/or nexts (which do the exact same thing in a naked block so mix them around to keep people guessing) and perhaps a last near the bottom. I mean you can do nearly anything with those almost like you can do anything with goto.

    But I'd much rather stick to abusing the "early return" which I don't find hard to parse at all:

    sub get_next_interesting_thing { do { return if ! get_next_thing; } until( interesting_thing ); return thing; } sub get_next_valid_thing { while( get_next_interesting_thing ) { return thing if valid_thing; instruct; } return; } if( ! get_next_valid_thing ) { complain; return; } do { do lots of stuff with thing; } while( get_next_valid_thing ); finish up; return result;
    (or if keeping track of having seen at least one valid thing is less work than retyping "get_next_valid_thing", then switch that bottom stuff around to look more like the original)

            - tye (but my friends call me "Tye")
      That way your code actually looks like "loop reading commands until you get a non-empty one and loop doing that until a 'quit' is requested". Not, "here is a block that I may or may not be using as a loop (please parse the rest of the block if you want to know), read a command, goto the top of the nearest surrounding loop (or naked block but not a conditional or do block) if the command is empty, otherwise go to the continue block (if there is one) or the top of the nearest surrounding loopish block, if a 'quit' was not requested". (:
      And for that, you use a "here's a do, please parse till the end to see whether I want to include a file, I want to turn a block into an expression or I want to use a looping construct"?

      At least with a bare block you know you have a looping construct. With do you can have 3 totally different things. On top of that, several of your examples nest dos. I can understand that some people find bare blocks confusing - I indicated you could use a while(1) in front of it - but I fail to see that nested do constructs are easier than a bare block.

      I would think that the major "problem" with my bare block is that the guard is at the end - but your do { } until suffers from the same problem.

      Here's how I read my block:

      • print prompt.
      • read line and chomp it.
      • try again if there are no non-blanks.
      • execute command and repeat if we don't quit.
      More or less the same as you would explain things in English. And isn't that what's a major design principle Larry uses for Perl? (Note that my next should have been a redo).

      -- Abigail

        Actually, you don't have to parse the rest of the do block to figure out what is going on. Once you see ";\ndo {\n" you know (from the ";" and newlines) that it isn't a (sensible) conversion of a block to an expression and (from the "{") that it isn't "do file".

        There was a smiley after the paragraph you quote. The point of that was to illustrate how hard the loop control shortcuts can be to parse. Your example was quite small so figuring out where each control change branched to was not difficult. I still prefer "do {" because it makes most people think "loop" much faster and because the branches stand out much more. Hiding a branch with something small that doesn't change indentation (like redo or goto) means that the structure of the code is not nearly as visually discernable.

        So I avoid naked blocks, redo, next, and last because they are easy to abuse and require more visual energy to parse. That doesn't mean I never use them, of course (though I've never found a use for naked block as a loop). For example, redo, next, and last are important tools for allowing you to use Perl's most natural looping construct (for(@list)) in more situations rather than resorting to a more error-prone alternative.

        As a few other examples of this have shown recently, pulling the "naked block plus redo" out for very simple problems can lead to producing much more complex code than is required because it doesn't put much pressure on you to figure out the structure of the code since they give you such abitrary control of the flow.

        And it certainly isn't a tool I would ever suggest to a person already having problems writing fairly simple code in a well-structured manner.

                - tye (but my friends call me "Tye")

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://95878]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others cooling their heels in the Monastery: (5)
As of 2024-03-29 15:06 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found