【问题标题】:Is it possible in Perl to require that a subroutine call be made?在 Perl 中是否可以要求进行子例程调用?
【发布时间】:2012-09-03 17:35:27
【问题描述】:

我对 Perl 的了解还不够,甚至不知道我要确切地要求什么,但我正在编写一系列子例程,以供许多单独的脚本使用,这些脚本都处理不同的传入平面文件。这个过程远非完美,但这是我必须处理的事情,我正在尝试为自己构建一个小型潜艇库,让我更容易管理这一切。每个脚本处理不同的传入平面文件,具有自己的格式、排序、分组和输出要求。一个常见的方面是我们有一些小的文本文件,其中包含用于命名输出文件的计数器,因此我们没有重复的文件名。

因为每个文件对数据的处理是不同的,所以我需要打开文件来获取我的计数器值,因为这是一个常见的操作,我想把它放在一个sub中来检索计数器。但随后需要编写特定的代码来处理数据。并且想要一个允许我在处理完数据后用计数器更新计数器的子程序。

如果第一个被调用,有没有办法让第二个子调用成为要求?理想情况下,它甚至可能是一个会阻止脚本运行的错误,就像语法错误一样。

编辑:这里有一些 [丑陋和简化的] 伪代码,以便更好地了解当前流程:

require "importLibrary.plx";

#open data source file
open DataIn, $filename;

# call getCounterInfo from importLibrary.plx to get 
# the counter value from counter file
$counter = &getCounterInfo($counterFileName);

while (<DataIn>) {
  # Process data based on unique formatting and requirements
  # output to task files based on requirements and name files 
  # using the $counter increment $counter
}

#update counter file with new value of $counter
&updateCounterInfo($counter);

【问题讨论】:

  • 您可以从第一个子程序调用第二个子程序(在第一个子程序中的所有错误处理完成之后)。或者,您可以使用第二个子程序检查第一个子程序是否成功运行的全局标志。或者,您可以将第一个 sub 中的指标作为参数之一传递给第二个 sub。蒂姆托迪! :)
  • 问题是我需要第一个 sub 来返回计数器,以便我的脚本可以使用计数器处理和输出文件。在处理完数据之前,我无法调用第二个来更新文件。如果在脚本中调用第一个而没有在脚本中进一步调用第二个,我想抛出一个错误。我不能在第二个中进行测试,因为如果它正在测试一个全局变量,这意味着第二个被调用,从而使全局变量测试没有意义。

标签: perl compiler-errors subroutine required


【解决方案1】:

我不太明白你在尝试什么,但你总是可以让你的 subs 可插拔:

我们有一个子process_file。它需要一个子程序作为参数来进行主要处理:

our $counter;
sub process_file {
   my ($subroutine, @args) = @_;
   local $counter = get_counter();
   my @return_value = $subroutine->(@args);
   set_counter($counter);
   return @return_value;
}
# Here are other sub definitions for the main processing
# They can see $counter and always magically have the right value.
# If they assign to it, the counter file will be updated afterwards.

假设我们有一个子process_type_A,那么我们可以这样做

my @return_values = process_file(\&process_type_A, $arg1, $arg2, $arg3);

除了额外的调用堆栈帧和$counter 设置之外,它的行为与process_type_A($arg1, $arg2, $arg3) 类似。

如果您更喜欢传递名称而不是 coderef,我们也可以安排。

package MitchelWB::FileParsingLib;
our $counter;
our %file_type_processing_hash = (
  "typeA" => \&process_type_A,
  "typeB" => \&process_type_B,
  "countLines" => sub { # anonymous sub
     open my $fh, '<', "./dir/$counter.txt" or die "cant open $counter file";
     my $lines = 0;
     $lines++ while <$fh>;
     return $lines;
  },
);

sub process_file {
   my ($filetype, @args) = @_;
   local $counter = get_counter();
   # fetch appropriate subroutine:
   my $subroutine = $file_type_processing_hash{$filetype};
   die "$filetype is not registered" if not defined $subroutine;  # check for existence
   die "$filetype is not assigned to a sub" if ref $subroutine ne 'CODE';  # check that we have a sub
   # execute
   my @return_value = $subroutine->(@args);
   set_counter($counter);
   return @return_value;
}
...;
my $num_of_lines = process_file('countLines');

编辑:最优雅的解决方案

或者:属性真的很整洁

为什么是愚蠢的回调?为什么要额外的代码?为什么要调用约定?为什么调度表?虽然它们都非常有趣和灵活,但还有一个更优雅的解决方案。我刚刚忘记了一点点信息,但现在它已经全部到位。 Perl 具有“属性”,在其他语言中称为“注释”,它允许我们对代码或变量进行注释。

定义一个新的 Perl 属性很容易。我们use Attribute::Handlers并定义了一个与你要使用的属性同名的sub:

sub file_processor :ATTR(CODE) {
  my (undef, $glob, $subroutine) = @_;
  no strict 'refs';
  ${$glob} = sub {
    local $counter = get_counter();
    my @return_value = $subroutine->(@_);
    set_counter($counter);
    return @return_value;
}

我们使用属性:ATTR(CODE) 来表示这是适用于子程序的属性。我们只需要两个参数,我们要注释的子程序的全名,以及子程序的代码引用。

然后我们关闭部分严格性以使用${$glob} 重新定义子。这有点高级,但它本质上只是访问内部符号表。

我们用上面给出的process_file 的简化版本替换了带注释的子。我们可以直接传递所有参数 (@_) 而无需进一步处理。

毕竟,我们在您之前使用的 subs 中添加了一点点信息:

sub process_type_A :file_processor {
  print "I can haz $counter\n";
}

... 它只是在没有进一步修改的情况下进行替换。使用库时这些更改是不可见的。我知道这种方法的限制,但你在编写普通代码时不太可能遇到它们。

【讨论】:

  • 我没有足够快地编辑我的评论:所以你的意思是我还需要定义更多的子例程。 get_counter() 将检索我的起始值, set_counter() 将我的最终结果写回我的文件,在这之间,我将调用一个唯一的 sub 来处理名为匹配 $subroutine 中的字符串的 sub 中的数据? $subroutine sub 中的内容将是我正在为给定文件处理的数据的独特之处?
  • @MitchelWB Perl 是一种函数式语言,因此我们可以将 coderefs/functions 作为参数传递给另一个子例程。在process_file 内部,我们将一个局部值分配给一个全局变量——这个局部值可以从在这个范围内调用的所有子程序中看到。是的,这就是我的建议。但是我们不应该传递函数名,而是传递给该函数的 coderef(我们采用带有 \&amp; 前缀的 coderef)。这使您可以在任何地方为新文件类型创建插件函数,并间接调用它。这种模式称为 callbackwrapper
  • @MitchelWB 我为“函数调度表”添加了一个示例。即,将名称与函数相关联的哈希。如果需要,您可以在运行时扩展哈希以添加新方法。
  • @MitchelWB 我添加了另一个解决方案。这更难以理解,但一旦实施,就可以轻松直观地使用。我使用属性来修改符号表。您可以咨询Attribute::Handlers documentation 了解更多信息。要在模块外部使用此属性,必须使用 use base MODULEuse parent MODULE 对其进行子类化。
  • 哇,我完全不明白! :) 但我非常感谢您的帮助。
【解决方案2】:

好吧,您可以设置一个全局标志并使用END block

也许更简洁类似于@amon 的提议,或者甚至只是将您的文件处理放在一个名为 sub 的标准中并从您的计数器代码中调用它。

my ($fh, counter) = get_counter(...);
my $ok = process_file($fh, $counter);
update_counter($counter) if $ok;

您的 process_file 将从模块中导出,如果您想保持简单,请使用 perl 的 -Μ 与您的 process_file 子一起加载模块。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-02
    • 1970-01-01
    相关资源
    最近更新 更多