13

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

我认为 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) ;

文档说我可以使用tables_dump方法和使用参数将输出转储到命令提示符$show_content$col_sep控制输出格式:

tables_report([$show_content, $col_sep])

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

tables_dump([$show_content, $col_sep])

除了将信息转储到 STDOUT 之外,与 tables_report() 相同。

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

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

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

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

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

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

$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 => '_'}) ;

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

4

7 回答 7

11

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

于 2012-11-26T02:30:24.037 回答
9

Perl 中的命名参数传递(即使使用默认值)在面向对象的 Perl的第 6 章中有很好的解释。这种风格非常重要,并且广泛用于对象构造函数中。这就是为什么在他们的 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";
}
于 2013-08-09T23:46:43.197 回答
4

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

于 2012-11-26T02:21:30.113 回答
4

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

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

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

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

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

由于没有第二个参数,因此没有分配任何内容$col_sep,因此它使用默认分隔符,如您所见。

于 2012-11-26T15:11:15.147 回答
3

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

# 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) {
       ...
于 2012-11-26T04:41:54.287 回答
2

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

use strict;
use Carp::Assert;

sub func {
   my (%hash) = @_;
   assert($hash{'baz'} == 1);
   assert($hash{'mu'} == 2);
}

func('baz' => 1, 'mu' => 2);
于 2017-04-17T21:19:27.823 回答
-1

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

#!/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;
}
于 2012-11-26T04:47:38.217 回答