【问题标题】:Does Perl support named parameters in function calls?Perl 是否支持函数调用中的命名参数?
【发布时间】:2012-11-13 12:19:17
【问题描述】:

根据我对支持该功能的语言的经验,使用命名参数而不是位置参数调用函数的程序更易于阅读和维护。

我认为 Perl 有这个功能,但它不适合我。

这是我正在使用的软件包的怪癖,还是我做错了?

设置函数调用

我的第一个 Perl 项目是使用 HTML::TableExtract 包从 HTML 标记中提取表格数据并将其显示为文本。

以下代码设置解析器:

use strict;
use warnings;
use HTML::TableExtract;

my $markup = <<MARKUP;
<table>
  <tr> <th>a</th> <th>b</th> <th>c</th> </tr>
  <tr> <td>1</td> <td>2</td> <td>3</td> </tr>
  <tr> <td>4</td> <td>5</td> <td>6</td> </tr>
</table>
MARKUP

my $parser = HTML::TableExtract->new() ;

$parser->parse($markup) ;

documentation 表示我可以使用tables_dump 方法将输出转储到命令提示符,并使用参数$show_content$col_sep 来控制输出格式:

tables_report([$show_content, $col_sep])

返回一个总结提取表的字符串,以及它们的深度和计数。可选地采用 $show_content 标志,它将转储每个表的提取内容以及由 $col_sep 分隔的列。默认 $col_sep 是 ':'。

tables_dump([$show_content, $col_sep])

与 tables_report() 相同,只是将信息转储到 STDOUT。

使用位置参数和命名参数调用

如果我按文档顺序传递位置参数,我会得到我期望的输出:

$parser->tables_dump(1, '_') ;

列用下划线而不是默认冒号分隔:

TABLE(0, 0):
a_b_c
1_2_3
4_5_6

按照 Perl.com 的Advance Subroutines article,我尝试传递一个包含参数名称和值的哈希来阐明参数的含义:

$parser->tables_dump({show_content => 1, col_sep => '_'}) ;

Perl 不明白这一点。它会忽略col_sep 的值并使用默认值输出:

TABLE(0, 0):
a:b:c
1:2:3
4:5:6

如果我不尝试更改分隔符,我会得到相同的输出:

$parser->tables_dump({show_content => 1}) ;

即使我指定了无意义的参数名称,我也会得到相同的输出:

$parser->tables_dump({tweedledum => 1, tweedledee => '_'}) ;

我可以使用命名参数样式调用这个函数,还是应该满足于位置?

【问题讨论】:

    标签: perl named-parameters


    【解决方案1】:

    Perl 本身不支持命名参数,但是可以设计函数来接受命名参数(作为散列或 hashref)。函数的作者如何实现它取决于函数的作者。您需要提供函数所期望的参数,否则您会得到意想不到的结果。

    【讨论】:

      【解决方案2】:

      Object Oriented Perl 第 6 章很好地解释了在 Perl 中传递的命名参数(即使使用默认值)。 这种风格非常重要,广泛用于对象构造函数中。这就是为什么在他们的 OO Perl 书中对其进行了解释。

      我将引用他们的两个例子:

      # This is how you call a subroutine using named argument passing
      interests(name => "Paul", language => "Perl", favourite_show => "Buffy");
      
      # This is how you define the subroutine to accept named arguments
      sub interests {
         my (%args) = @_;
      
         # This is how you capture named arguments and define
         # defaults for the ones missing from a particular call.
         my $name           = $args{name}           || "Bob the Builder";
         my $language       = $args{language}       || "none that we know";
         my $favourite_show = $args{favourite_show} || "the ABC News";
      
         print "${name}’s primary language is $language. " .
         "$name spends their free time watching $favourite_show\n";
      }
      

      另一个给出定义默认值的不同方式(在哈希中)的示例是:

      my %defaults = ( pager => "/usr/bin/less", editor => "/usr/bin/vim" );
      
      sub set_editing_tools {
          my (%args) = @_;
      
          # Here we join our arguments with our defaults. Since when
          # building a hash it’s only the last occurrence of a key that
          # matters, our arguments will override our defaults.
          %args = (%defaults, %args);
      
          # print out the pager:
          print "The new text pager is: $args{pager}\n";
      
          # print out the editor:
          print "The new text editor is: $args{editor}\n";
      }
      

      【讨论】:

      • 我不是在寻找一种方法来创建一个接受命名参数的模块;我认为 HTML::TableExtract 模块已经接受了命名参数。这个问题是基于一个错误的前提。但是,当我开始编写自己的 Perl 模块时,面向对象的 Perl 文档将非常有用。感谢分享。
      【解决方案3】:

      在 Perl.com 的 Advance Subroutines 文章之后,我尝试通过 散列包含参数名称和值,以阐明含义 参数:

      那篇文章介绍了一种编写子例程的方法,以便它们接受命名参数的 hashref。如果您调用的 sub 没有被写入接受它,那么它将不知道如何正确处理它。

      $parser->tables_dump({show_content => 1, col_sep => '_'}) ;
      

      Perl 不明白这一点。它忽略 col_sep 的值和 输出默认值:

      不要过于迂腐,但 Perl 明白这一点。但是,tables_dump 仅用于接受标量参数列表。当您以这种方式调用它时,它会接收一个标量参数。这个参数恰好是对哈希的引用,但tables_dump 不知道也不关心它,所以它使用引用作为$show_content 的值。这可能相当于将1 传递给show_content,因为1 和任何可能的引用在布尔上下文中都将评估为“真”,我假设$show_content 只用作布尔值。

      由于没有第二个参数,没有任何东西被分配给$col_sep,所以它使用默认分隔符,正如您所观察到的那样。

      【讨论】:

      • 感谢您解释观察到的行为。你是对的,但是 Perl 的理解不是我的意思! 归根结底,是我不理解 Perl。
      【解决方案4】:

      Perl 没有对命名参数的内置支持。如果要使用它们,则必须专门编写函数以接受该样式的参数。所以你必须满足于位置参数(或编写一个包装函数(可能在一个子类中))。

      【讨论】:

        【解决方案5】:

        这不是太复杂。您可以像散列或散列引用一样传递键值对,并在子例程中将参数加载到散列或散列引用中。

        # called like $parser->tables_dump({show_content => 1, col_sep => '_'}) ;
        sub TheParser::tables_dump {
            my ($self, $args) = @_;
            if ($args->{show_content} == 1) {
                print join $args->{col_sep}, $self->the_column_data();
                ...
            }
        }
        

        通过另一行,您可以将您知道的命名参数加载到适当命名的变量中:

            my ($self, $args) = @_;
            my ($show_content, $col_sep) = @$args{qw(show_content col_sep)};
            if ($show_content == 1) {
               ...
        

        【讨论】:

        • 对不起,我不明白。您是在向我展示如何使用接受命名参数的模块方法包装模块方法吗?
        • 不,这是编写一个需要命名参数的新方法的方法。抱歉,如果这不是您所要求的。
        • 我不是模块的维护者。我很乐意接受它所具有的任何限制。在考虑为其他人的代码提交补丁之前,我有很多关于 Perl 的知识要学习。尽管如此,感谢您的帮助!你的 sn-ps 给了我一些学习的指点。
        【解决方案6】:

        离开标题的问题,您可以执行以下操作:

        use strict;
        use Carp::Assert;
        
        sub func {
           my (%hash) = @_;
           assert($hash{'baz'} == 1);
           assert($hash{'mu'} == 2);
        }
        
        func('baz' => 1, 'mu' => 2);
        

        【讨论】:

          【解决方案7】:

          这是我编写的一个简单程序,它可以使用一种命名参数。它允许使用默认值。

          #!/usr/bin/perl
          use strict;
          use warnings;
          use 5.014;
          use POSIX qw/ strftime /;
          
          # Script to get prior Monday (if today is Mon, then Mon a week ago).
          
          for my $day (qw/ Sun Mon Tue Wed Thu Fri Sat /) {
              say "Last $day: ", last_monday( day => $day );  
          }
          
          sub last_monday {
              my %arg =  ( time => [localtime],
                           day  => 'mon',
                           span => 1,
                           @_
                         );
              my $dow; # day of week
          
              if    ('sunday'    =~ /$arg{day}/i) { $dow = 0}
              elsif ('monday'    =~ /$arg{day}/i) { $dow = 1}
              elsif ('tuesday'   =~ /$arg{day}/i) { $dow = 2}
              elsif ('wednesday' =~ /$arg{day}/i) { $dow = 3}
              elsif ('thursday'  =~ /$arg{day}/i) { $dow = 4}
              elsif ('friday'    =~ /$arg{day}/i) { $dow = 5}
              elsif ('saturday'  =~ /$arg{day}/i) { $dow = 6}
              else {
                  warn "$arg{day} is not a valid day of week. $!";
                  return;
              }
          
              my ($wday, @dmy) = @{ $arg{time} }[6, 3..5];
          
              # (will work across month, year boundries)
              $dmy[0] -= ($wday - $dow) % 7 || ($arg{span} ? 7 : 0); # $dmy[0] == mday
              return strftime "%Y%m%d", 0,0,0,@dmy;
          }
          

          【讨论】:

          • 你能在你的答案中使用更多的英语吗?我对 Perl 的理解还不够深入,看不出它是如何回答我的问题的。
          • @isme 对不起,我的答案很糟糕。我没有仔细阅读你的问题。如果我可以删除我的帖子,我会的。
          猜你喜欢
          • 2013-10-08
          • 2012-12-04
          • 1970-01-01
          • 2017-06-30
          • 2015-12-21
          • 2010-12-15
          • 1970-01-01
          • 1970-01-01
          • 2013-01-17
          相关资源
          最近更新 更多