Re: Subroutine references inside of a hash with arguments.
by ikegami (Patriarch) on Jul 24, 2009 at 22:35 UTC
|
"1" => sub { get_ifc_name({'ifc_default' => 'test_me',}) },
As an aside, numbers for hash keys? red flag! You should be using an array here, not a hash. | [reply] [d/l] |
|
Those were temporary and are to be replaced by menu names. I made the simplest example of the problem I was experiencing that I could.
| [reply] |
Re: Subroutine references inside of a hash with arguments.
by ig (Vicar) on Jul 24, 2009 at 23:17 UTC
|
"1" => \&get_ifc_name,
the hash value is a reference to the subroutine get_ifc_name, but with
"1" => \&get_ifc_name({'ifc_default' => 'test_me',}),
the hash value is a reference to whatever that subroutine returns when called with the specified argument. Even if the subroutine returns a reference to a subroutine, it will be inconsistent with the previous case.
Maybe, instead of a single value for each element of the %menu_hash hash, you should have a reference to a hash, something like:
"1" => { sub => \&get_ifc_name, args => {'ifc_default' => 'test_me',}
+},
Later, you can call this with
$menu_hash{1}{sub}->($menu_hash{1}{args});
| [reply] [d/l] [select] |
|
What's the advantage of that over what I posted?
| [reply] |
|
It is a little easier to modify the arguments independently of the subroutine to be called, at the cost of a more complex data structure and more complex subroutine calls.
One way this could be done, as the subroutine is called, is as follows:
$menu_hash{1}{sub}->({
%{$menu_hash{1}{args}}, # default arguments/values
ifc_default => 'overridden', # override default value
ifc_color => 'green', # add an argument/value
});
There would be no point including the arguments hash from $menu_hash if, as in this case, it included only one element which was being overridden, but it could have other elements, only some of which are overridden in a given call.
The arguments could be set or changed independently of either setting or calling the subroutine to be called.
Whether these are advantages or not will depend on the situation. The name 'ifc_default' suggests that there might be situations where non-default arguments might be passed.
But I think I like the method LanX suggested better than my own, now that I think about it.
update: and then I realized that what your suggested is very similar to what LanX suggested, and additional/override arguments can easily be included in your approach.
"1" => sub { get_ifc_name({'ifc_default' => 'test_me', %{shift} }) },
after which
$menu_hash{1}->({ifc_default => 'overridden', color => 'green'});
So, after all, I like my own suggestion least of all. Thanks for questioning it.
| [reply] [d/l] [select] |
|
|
Re: Subroutine references inside of a hash with arguments.
by LanX (Saint) on Jul 24, 2009 at 23:04 UTC
|
use strict;
use Data::Dumper;
sub get_ifc_name {
my %hash=@_;
print Dumper \%hash;
}
sub setdefaults {
my ($func,@defs)=@_;
return sub {
$func->(@defs,@_)
}
}
my $f2= setdefaults( \&get_ifc_name, one =>1);
$f2->(two =>2);
OUTPUT
$VAR1 = {
'one' => 1,
'two' => 2
};
FOOTNOTES:
(1) IIRC it's called "currying". | [reply] [d/l] [select] |
|
Just noticed your passing hashes as refs and not as lists, so better do that:
use strict;
use Data::Dumper;
sub get_ifc_name {
my ($hash)=@_;
print Dumper $hash;
}
sub setdefaults {
my ($func,$defs)=@_;
return sub {
my ($hash)=@_;
$func->({%$defs,%$hash})
}
}
my $f2= setdefaults( \&get_ifc_name, {one =>1});
$f2->({two =>2});
__DATA__
#OUTPUT
$VAR1 = {
'one' => 1,
'two' => 2
};
The two hashes will be flattened and reference of the joined hashes will be passed to your sub.
Please note, that the defaults can be overridden by keys of the same name. (I think this is anyway the idea of defaults :-)
| [reply] [d/l] |
Re: Subroutine references inside of a hash with arguments.
by Marshall (Canon) on Jul 25, 2009 at 00:32 UTC
|
First some points that will help you forever:
#!/usr/bin/perl
use warnings;
# can be simplified to just #!/usr/bin/perl -w
# this will work even on Windows!! Windows Perl doesn't look
# at the path, but it does look at the -w trailing thing.
You will see this often at the start of a Perl program:
#!/usr/bin/perl -w
use strict;
Warnings has a runtime impact, but normally I would "use warnings"
except in the case of very well debugged, high performance code and
even then I would think twice about leaving warnings out!
I would add "use strict;". Strict is a compile time thing and
has no impact upon code execution time. It enforces scoping rules
as one of the main things. Your code doesn't compile
under "strict".
The syntax: &sub_name is deprecated. You can just say
"sub_name;" or "sub_name();, or sub_name($parm1, $parm2)".
sub menu_system($bs_index){....} is wrong in Perl.
Perl does have a prototype mechanism but is not
strongly typed (to say the least!). In any event the Perl prototype
mechanism could say something like: I require at least 3 and
possibly 4 scalar values. Calling the sub with 2 or 5 values would be
rejected during compile. Most Perl programs don't even worry about this
and for your first code, I would wouldn't either and I'm not even going
to demonstrate this syntax because I don't think you need it.
A Perl sub gets a "stack" of stuff. The Perl subroutine does what it
wants with this "stack of stuff".
Update: I forgot to add this sub param stuff:
sub A
{
my $parm1 = shift; #effect: literally shift off the stack passed
}
sub B
{
my ($p1,$p2,$p3) = @_; #effect: make copy from stack passed
}
I am a bit confused with your code, but I will present the Perl way
of a standard command loop that uses text as a command:
while ( (print "Enter Command: "),
(my $line = <STDIN>) !~ /^\s*q(uit)?\s*$/i
)
{
next if $line =~ /^\s*$/; #skip new lines
next if $line =~/^\s*skip/i; #skip is a no op command
insert if $line =~/^\s*insert/i; #do insert command
delete if $line =~/^\s*delete/i; #do delete command
#...etc..
}
sub insert()
{...}
sub delete()
{...}
There are of course many variations on the above. But it is short and to the point.
I recommend not trying to be overly complex. I have written fancy hash driven dispatch
tables, but I don't think that is what you need.
| [reply] [d/l] [select] |
|
use warnings; can be simplified to just #!/usr/bin/perl -w
That's not true; the warnings pragma has lexical effects. The -w flag does not.
Strict is a compile time thing and has no impact upon code execution time.
Strict reference checking occurs at runtime.
It enforces scoping rules as one of the main things.
strict has nothing to do with scoping rules. You can use fully qualified package globals everywhere and strict will not complain.
The syntax: &sub_name is deprecated.
Discouraged, perhaps (except for tail calls) but not deprecated.
You can just say "sub_name;"
Not always -- if you have strict enabled.
#effect: literally shift off the stack passed
This is misleading. (How do you pass a stack?)
#effect: make copy from stack passed
This is doubly misleading.
| [reply] [d/l] [select] |
|
I would be curious to learn what lexical effects the "use warnings" pragma has that the -w flag doesn't? I don't think that there are any at all. There are some differences that I seem to remember...maybe you can turn warnings off, if the -w flag is used but not if "use warnings" is in the source code, for example, maybe?
Strict is a compile time thing - not run time. Scoping like "my vars" is a big part of what "use strict" enforces - use of a fully qualified name in another module is completely within the "rules of strict". I would highly recommend Randal Schwartz'es book, "Effective Perl Programming", see item 36. For example to learn more about -w, see page 143.
I don't think we need to get into a big brouhaha about legalistic sounding terminology about "discouraged" vs "deprecated". Suffice it to say that neither of us would recommend old style &sub() over just sub() for new code. I didn't explain var passing very well, fair enough. I can write an article about this if there is enough interest. There are limits to what can be explained in a short post. And there are some extremely complex things that have to do with this. But yeah, in general when you call a sub, you push things onto a stack and the sub consumes them by shifting them off the stack, just like with any other main programming language, C, C++, JAVA, etc. Yes, there are things that are different things about say how C and C++ and Perl does this, but these are details. The basic "model" about how to send stuff to a sub and get stuff back is the same.
| [reply] [d/l] |
|
|
|
|
Re: Subroutine references inside of a hash with arguments.
by Marshall (Canon) on Jul 27, 2009 at 20:35 UTC
|
I am trying again... with the intent of trying to keep things "easy".
Use a simple command loop like shown below to get user command and do the re-prompting. If you have say just a dozen commands or so, there isn't any need for a fancy hash driven dispatch table. The 12 "if" statements will be plenty fast for the UI especially if you order them in expected frequency of occurence. If there are sub parms on that command line then give that job to the command subroutine by giving it the original command line or part of it (can strip off "command" from $line before calling sub(), but I wouldn't - if sub has to parse anyway or have other UI interactions let it do it all)
If the defaults to the command are just supplied in the menu routine, then I would simply put them in the sub (why pass the same defaults all the time? could be some good reasons if sub() is used in other contexts, but that's not the case here?). If passing an anon hash ref, then just go ahead and make a local copy of this thing to simplify the subroutine code (no ref to hash in the rest of code - no need to get fancy when you are learning or when performance won't matter). Code below shows passing anon hash and using defaults in sub.
If one of the "command subs" has its own user interaction, then it is responsible for error message and main command loop will just re-prompt.
#!/usr/bin/perl -w
use strict;
print "************************************\n";
print "* test - shift9999\n";
print "* Version 0.1 - \"The mule\"\n";
print "************************************\n";
print "\n";
while ( (print "\nEnter Command...: "),
(my $line = <STDIN>) !~ /^\s*q(uit)?\s*$/i
)
{
next if ( $line =~ /^\s*$/); #skip blank lines
#and reprompt w/o error msg
chomp ($line);
#I would be these if statements on one line, but here they are too
+long for that...
if ($line =~/^\s*one/i)
{one($line,{parm1 => 'abc'});} # "one" command
elsif
($line =~/^\s*two/i)
{two($line);} # "two" command
#...etc..
else {print "Invalid command entry!\n";}
}
sub one
{
my $line = shift;
my $default_href = shift;
my %defaults = %$default_href;
print "sub one has $line as input line\n";
foreach my $default ( keys %defaults)
{
print "default: $default is $defaults{$default}\n";
}
}
sub two
{
my $line = shift;
print "subroutine two has $line as input line\n";
my %defaults = (xyz => '123', ghig => '446');
foreach my $default ( keys %defaults)
{
print "default: $default is $defaults{$default}\n";
}
}
__END__
Example Interaction:
Enter Command...: one
sub one has one as input line
default: parm1 is abc
Enter Command...: two
subroutine two has two as input line
default: ghig is 446
default: xyz is 123
Enter Command...: three
Invalid command entry!
Enter Command...: quit
| [reply] [d/l] |
|
Thanks for all the great responses! I have not had a chance to read through all of them but I did get the script working the way I wanted, albeit, maybe not the most efficient way.
I have changed the hash into an array as I dropped the menu names early on but never changed the hash. I am going solely off of Indexes now (thanks for pointing that out ikegami!).
I then used an eval() on the menu subroutine names in the array elements rather than the $menu_hash{$bs_index}->();
I have attached a simple example of the script without the neat menu navigation back\forward\restart\ options that are in the real deal. This is just an example so don't expect a whole lot of error checking and polish.
#!/usr/bin/perl
use warnings;
print "********************************************\n";
print "* test app\n";
print "* Version 0.2 - \"The mule\"\n";
print "********************************************\n";
print "\n";
#Define global args
my @menu_list = (
"&default",
"&get_ifc_name({'ifc_default' => 'test.ifc0'})",
"&get_ip_address({'ipaddr_default' => '10.20.1.1'})",
"&headr_section",
);
&menu_system;
sub menu_system(){
#Define our menu system here with the appropriate subs and thier argum
+ents
$bs_index = shift;
#first run check to setup the first menu
if(!$bs_index){
#start things off at the first menu
$bs_index = "1";
}
print "\n*** DBG MENU SYSTEM: Index from args: $bs_index ***";
#check to see if the menu index is defined, otherwise display an error
+.
if (defined $menu_list[$bs_index]){
#menu is valid, run the sub
print "\n*** DBG MENU SYSTEM: running ID: $bs_index ***\n\n"
+;
eval($menu_list[$bs_index]);
}
else
{
print "*** Unknown Menu Called***\n";
}
}
sub get_ip_address()
{
my($arg) = shift;
print "IP for the Interface [ \"$arg->{'ipaddr_default'
+}\" ]\n";
$ip_addr = <STDIN>;
chomp($ip_addr);
&menu_system("3");
}
sub get_ifc_name()
{
my($arg) = shift;
print "Enter an Interface Name [ \"$arg->{'ifc_default'
+}\" ]\n";
$device_name = <STDIN>;
chomp($device_name);
&menu_system("2");
}
sub headr_section{
print "*******************************************\n";
print "* Section 2 Header\n";
print "* To skip a step, type \"skip\"\n";
print "* To go back a step, type \"back\"\n";
print "* To restart this step, type \"restart\"\n";
print "********************************************\n\n";
}
This may not be the best way to do this but it is working for me without any side effects so far. Any pointers are welcome! I don't mind the criticism. | [reply] [d/l] [select] |