【问题标题】:Perl: dispatch hashes and shared variablesPerl:分派散列和共享变量
【发布时间】:2011-11-15 17:58:33
【问题描述】:

我有一个带有一组函数的模块,这些函数被实现为带有辅助函数的调度散列:

my $functions = {
  'f1' => sub { 
      my %args = @_;
      ## process data ...
      return $answer; 
  },
[etc.]
};

sub do_function {
    my $fn = shift;
    return $functions->{$fn}(@_);
}

这被一些处理制表符分隔数据的脚本使用;正在检查的列由适当的子程序转换。在处理列中的值时,我将数据哈希传递给 sub,它会生成一个标量,即该列的新值。

目前潜艇是这样称呼的:

my $new_value = do_function( 'f1', data => $data, errs => $errs );

并且参数中的变量都被声明为'my' - my $data、my $errs 等。是否可以更新传递给 subs 的参数中的其他值而不必返回它们?即不必这样做:

 ... in $functions->{f1}:
      my %args = @_;
      ## process data ...
      ## alter $args{errs}
      $args{errs}->{type_one_error}++; 
      ## ...
      return { answer => $answer, errs => $args{errs} }; 
 ...

 ## call the function, get the response, update the errs
 my $return_data = do_function( 'f1', data => $data, errs => $errs );
 my $new_value = $return_data->{answer};
 $errs = $return_data->{errs}; ## this has been altered by sub 'f1'

我可以这样做:

  my $new_value = do_function( 'f1', data => $data, errs => $errs );
  ## no need to update $errs, it has been magically updated already!

【问题讨论】:

    标签: perl variables scope anonymous-function


    【解决方案1】:

    您可以传递对值的引用并在子例程中对其进行更新。

    例如:

    sub update {
        my ($ref_to_value) = @_;
        $$ref_to_value = "New message";
        return "Ok";
    }
    
    my $message = "Old message";
    
    my $retval = update(\$message);
    
    print "Return value: '$retval'\nMessage: '$message'\n";
    

    据我从您的代码 sn-ps 中可以看出,$errs 已经引用了哈希。 所以,实际上,您所要做的就是注释掉$errs = $return_data->{errs}; 行并尝试

    如果我的代码正确,$errs 就会更新。然后你应该将你的返回值更改为$answer 并执行以下操作:

    my $new_value = do_function( 'f1', data => $data, errs => $errs );
    

    【讨论】:

    • 是的,它使我的 perl 生活变得非常愉快。谢谢!
    【解决方案2】:
    • 首先将do_function 的定义更改为:

      sub do_function {
          my $fn = shift;
          goto &{$functions->{$fn}}
      }
      

      这是分派到新子例程的正确方法。这种形式的goto 用新的coderef 替换当前正在执行的子例程,不变地传递@_,并从调用堆栈中删除do_function(所以caller 工作正常)。您可能还需要在那里进行一些错误检查,以确保 $fn 是一个有效的名称。

    • 在你的函数中,你可以直接修改@_的单元格,你不需要通过引用传递任何东西(因为perl已经为你做了)。

      sub add1 {$_[0]++}
      my $x = 1;
      add1 $x;
      say $x; # 2
      

      要支持key => value 参数而不通过引用传递,您可以这样写:

      in $functions->{f1}:
        my %args;
        while (@_) {
            $args{$_} = /errs/ ? \shift : shift for shift
        }
        ## process data ...
        ## alter ${$args{errs}}
        ## ...
      
    • 但是因为在你的情况下$errs 是一个哈希引用,你不需要做任何额外的工作。所有引用都通过引用自动传递。在您现有的代码中,您所要做的就是修改 $args{errs} 的键(就像您现在所做的那样),它将修改对该哈希的每个引用。

      如果你想要一个函数本地散列,你需要复制散列*:

      my %errs = %{$args{errs}};
      

      其中%errs 是私有的,完成后,您可以使用$args{errs}{...} = ...; 将任何想要公开的值推送到$args{errs}。但请务必不要将$args{errs} 替换为副本(如$args{errs} = \%errs),因为这会破坏与调用者错误哈希的连接。如果要复制所有新值,可以使用以下之一:

      %{$args{errs}} = %errs;                             # replace all keys
      @{$args{errs}}{keys %errs} = values %errs;          # replace keys in %errs
      ... and $args{errs}{$_} = $errs{$_} for keys %errs; # conditional replace
      

      *或本地化部分/全部键

    【讨论】:

    • 感谢您的全面回复!
    • 我今天再次使用了这段代码——再次感谢您提供的非常有用的解释!
    猜你喜欢
    • 2012-05-10
    • 1970-01-01
    • 2014-10-13
    • 2021-10-13
    • 1970-01-01
    • 2014-11-30
    • 2016-08-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多