开发者

Binding values to functions in Perl

开发者 https://www.devze.com 2022-12-13 17:24 出处:网络
I am using a hash table similar to the following to store the letters that can be entered at a prompt along with that option\'s description and the function that will be called.

I am using a hash table similar to the following to store the letters that can be entered at a prompt along with that option's description and the function that will be called.

my %main_menu = (
    "l" => ["list locks", \&list_locks],
    "n" => ["new lock", \&new_lock],
    "u" => ["update lock", \&update_lock]
    );

menu_prompt(\%main_menu) produces the following menu:

 ________________________ 
| l - list locks         |
| n - new lock           |
| u - update lock        |
|________________________|
(l,n,u)> 

When the user enters 'u', at the prompt, the update_lock function will be called.

Now, I want to produce a similar menu with a new hash table (%lock_menu). However, I will first prompt the user for the ID of the lock they wish to update.

Please enter the ID of a lock to update: 1

You are updating lock 1.
 __________________ 
| l - list users   |
| n - new user     |
|__________________|
(p,u,c)> 

I want to store the lock ID so that it is accessible to the lock menu functions. For example:

my %users_menu = (
 "l" => ["list users", \&list_users],
 "n" => ["new user", \&new_user]);

I can't figure out how开发者_JAVA百科 to "attach" the lock ID to the functions in the %users_menu. So when 'l' is selected, list_users will be called with that number as its first argument.

I seem to remember that ML if you call a n-argument function in the ML language with only one argument it will produce a function that takes n-1 arguments. So for example, calling func(int,int,int) as func(5) would produce func(int,int) with the first argument saved as 5.

Is this possible in Perl? Or am I going about this the wrong way? Please let me know.

UPDATE: This is the function that prints a menu (print_options), prompts the user for a letter, and calls the corresponding function.

sub menu_prompt
{
    my $options = shift;

    print_options $options;

    my $choice = <>;
    chomp $choice;

    if (defined $$options{$choice})
    {
        $$options{$choice}[1](); # no arguments
    }
}

I would like to find a way to use this function for all menus, rather than writing a separate function where a value is passed to the function.


without posting more example code its hard to give a complete answer, but when you call your sub from the hash, why not pass it the lock value?

my $num = ... # get lock number;

$users_menu{"n"}[1]->($num)

# calls "new_user" passing it $num

question edited:

sub menu_prompt {
    my $options = shift;

    print_options $options;

    my $choice = <>; # i assume the diamond operator got stripped
    chomp $choice;   # second my is in error

    if (defined $$options{$choice}) {
        return $$options{$choice}[1](@_); 
             # any additional arguments to menu_prompt will be passed to the sub
             # return the value for future processing
    }
}


You are wanting to curry functions.

There are many CPAN modules (see end of post) for currying. Here's an example of curry by closure.

sub curry {
    my $f = shift;
    my $a = shift;
    sub { $f->( $a, @_ ); }
}


my ($input, $func);
$input = 2;
$func  = curry( sub { print join "\n", @_ }, $input );

$input = 12;
$func  = curry( $func , $input );

$input = 99;

$func->(4,6,8,10,19);


#outputs
2
12
4
6
8
10
19

Also see Data::Util, Sub::Curried, and Sub::Curry.

HTH


I have to say I don't completely understand your question, but anonymous subroutines might help you

my $id = (somehow get the ID);

my %users_menu = (
    "l" => ["list users", sub {list_users($id)}], 
                   #now, the id is fixed and the subroutine can be called without arguments
    "n" => ["new user", \&new_user]);


You can use a closure. A closure is basically a function which can "still see" some external variables. For your example, you'd use something like this:

sub make_list_users {
  my ($lock_id) = @_;
  return sub {
     <body of list_users>
     <do something with $lock_id here>
  }
}

This function returns an anonymous function that "closes over" $lock_id, hence the name "closure". And then instead of writing

my %users_menu = (
 "l" => ["list users", \&list_users],
 "n" => ["new user", \&new_user]);

you write

my %users_menu = (
 "l" => ["list users", make_list_users($id)],
 "n" => ["new user", make_new_user($id)]);

Note the absence of \&, we want the code executed right here. Your code still calls

$$options{$choice}[1](); # no arguments

since the result of make_list_users is a subroutine reference, as before, except that it has the lock id as "insider information". And no, $lock_id is not a global variable, it's a simple local variable of make_list_users, and will be set anew each time make_list_users is run. The only trick is that the returned subroutine remembers the value it had.

Closures are actually even more powerful than that: a subroutine can also change (assign to) any variables it closes over, without affecting the closed-over variables that other instances use (i.e. you can have two distinct subroutines returned from make_list_users around at the same time).

Also, two or more closures that close over the same variable both see the same instance of this variable, and can use this "secret channel" to pass messages to each other!

See also What's a closure?

0

精彩评论

暂无评论...
验证码 换一张
取 消