Perl是否支持在函数调用中使用命名参数?

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])

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

使用位置和名称参数进行调用

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

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

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

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

在Perl.com的高级子程序文章之后,我尝试传递一个包含参数名称和值的哈希表,以澄清参数的含义:

$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 => '_'}) ;
我可以使用命名参数风格来调用这个函数吗,还是只能使用位置参数?
7个回答

11

Perl本身不支持命名参数,但可以设计函数接受命名参数(作为哈希或哈希引用)。如何实现由函数的作者决定。您需要提供函数期望的参数,否则会得到意外结果。


9
在Perl中,即使有默认值,也很好地解释了Object Oriented 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";
}

1
我并不是在寻找一种创建接受命名参数的模块的方法;我认为 HTML::TableExtract 模块已经接受了命名参数。这个问题基于错误的前提。然而,当我开始编写自己的 Perl 模块时,面向对象的 Perl 文档将非常有用。感谢分享。 - Iain Samuel McLean Elder

4
在 Perl.com 的高级子程序文章之后,我试图传递一个包含参数名称和值的哈希来解释参数的含义:该文章介绍了一种编写子程序的方法,使其接受命名参数的哈希引用。如果您调用的子程序不是编写为接受它的,则它将无法正确处理。
$parser->tables_dump({show_content => 1, col_sep => '_'}) ;

Perl doesn't understand this. It ignores the value of col_sep and outputs with the default value:

并不是要过于追求细节,但Perl可以正常理解。然而,tables_dump只接受一系列标量参数的列表。当你这样调用它时,它会收到一个标量参数。这个参数恰好是哈希表的引用,但是tables_dump并不知道也不关心这一点,所以它使用引用作为$show_content的值。这可能等同于将1传递给show_content,因为在布尔上下文中,1和任何可能的引用都将被视为“true”,我假设$show_content仅用作布尔值。
由于没有第二个参数,所以没有任何东西被分配给$col_sep,因此它使用默认分隔符,就像你观察到的那样。

1
感谢您解释这种观察到的行为。您是正确的,但是 Perl 的理解并不是我“想要的”!归根结底,是我自己不理解 Perl。 - Iain Samuel McLean Elder

4

Perl没有内置对命名参数的支持。如果要使用它们,函数必须明确编写以接受该风格的参数。因此,你只能使用位置参数(或编写包装函数(可能在子类中))。


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) {
       ...

抱歉,我不明白。您是在向我展示如何使用接受命名参数的方法包装模块方法吗? - Iain Samuel McLean Elder
不,这是如何编写一个期望命名参数的新方法。如果这不完全是您要求的,请见谅。 - mob
1
我不是这个模块的维护者。我很乐意接受它所具有的任何限制。在考虑为别人的代码提交补丁之前,我还有很多关于Perl的学习要做。尽管如此,感谢你的帮助!你的片段给了我一些学习指针。 - Iain Samuel McLean Elder

2

根据标题的问题,您可以尝试以下操作:

use strict;
use Carp::Assert;

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

func('baz' => 1, 'mu' => 2);

-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;
}

你能在回答中使用更多的英语吗?我不太了解Perl,无法看出这如何回答我的问题。 - Iain Samuel McLean Elder
@isme 对不起,我的回答很差。我没有仔细阅读你的问题。如果我能删除我的帖子,我会这样做。 - Chris Charley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接