gpt4 book ai didi

perl - 采用可选 block 参数的子例程

转载 作者:行者123 更新时间:2023-12-04 03:37:25 25 4
gpt4 key购买 nike

与接受的原型(prototype)相关的注意事项尽管如此c,以下两个人为的 sub 是否可以存在于同一个包中,即提供一个可选的 block 参数,如 sort做?

sub myprint {
for (@_) {
print "$_\n";
}
}
sub myprint (&@) {
my $block = shift;
for (@_) {
print $block->() . "\n";
}
}

目的是提供与 sort 类似的调用约定。 ,例如允许执行:
my @x = qw(foo bar baz);
print_list @x;

# foo
# bar
# baz

...和:
my @y = ( {a=>'foo'}, {a=>'bar'}, {a=>'baz'} );
print_list { $_->{a} } @y;

# foo
# bar
# baz

如果我尝试(这是合理的),我会收到重新定义和/或原型(prototype)不匹配警告。

我想我可以这样做:
sub myprint {
my $block = undef;
$block = shift if @_ && ref($_[0]) eq 'CODE';
for (@_) {
print (defined($block) ? $block->() : $_) . "\n";
}
}

...但是 &@原型(prototype)提供语法糖;删除要求:
my @y = ( {a=>'foo'}, {a=>'bar'}, {a=>'baz'} );
print_list sub { $_->{a} }, @y; # note the extra sub and comma

(我试过 ;&@ ,但无济于事 - 它仍然产生 Type of arg 1 to main::myprint must be block or sub {} (not private array) 。)

最佳答案

是的。
不幸的是,这有点痛苦。您需要使用 Perl 5.14 中引入的关键字 API。这意味着您需要在 C 中实现它(以及对它的自定义解析)并使用 XS 将它链接到 Perl。
幸运的是 DOY 为 Perl 关键字 API 编写了一个很棒的包装器,允许您在纯 Perl 中实现关键字。没有C,没有XS!它被称为 Parse::Keyword .
不幸的是,这有处理封闭变量的主要错误。
幸运的是,它们可以使用 PadWalker 来解决。 .
无论如何,这是一个例子:

use v5.14;

BEGIN {
package My::Print;
use Exporter::Shiny qw( myprint );
use Parse::Keyword { myprint => \&_parse_myprint };
use PadWalker;

# Here's the actual implementation of the myprint function.
# When the caller includes a block, this will be the first
# parameter. When they don't, we'll pass an explicit undef
# in as the first parameter, to make sure it's nice and
# unambiguous. This helps us distinguish between these two
# cases:
#
# myprint { BLOCK } @list_of_coderefs;
# myprint @list_of_coderefs;
#
sub myprint {
my $block = shift;
say for defined($block) ? map($block->($_), @_) : @_;
}

# This is a function to handle custom parsing for
# myprint.
#
sub _parse_myprint {

# There might be whitespace after the myprint
# keyword, so read and discard that.
#
lex_read_space;

# This variable will be undef if there is no
# block, but we'll put a coderef in it if there
# is a block.
#
my $block = undef;

# If the next character is an opening brace...
#
if (lex_peek eq '{') {

# ... then ask Parse::Keyword to parse a block.
# (This includes parsing the opening and closing
# braces.) parse_block will return a coderef,
# which we will need to fix up (see later).
#
$block = _fixup(parse_block);

# The closing brace may be followed by whitespace.
#
lex_read_space;
}

# After the optional block, there will be a list
# of things. Parse that. parse_listexpr returns
# a coderef, which when called will return the
# actual list. Again, this needs a fix up.
#
my $listexpr = _fixup(parse_listexpr);

# This is the stuff that we need to return for
# Parse::Keyword.
#
return (

# All of the above stuff happens at compile-time!
# The following coderef gets called at run-time,
# and gets called in list context. Whatever stuff
# it returns will then get passed to the real
# `myprint` function as @_.
#
sub { $block, $listexpr->() },

# This false value is a signal to Parse::Keyword
# to say that myprint is an expression, not a
# full statement. If it was a full statement, then
# it wouldn't need a semicolon at the end. (Just
# like you don't need a semicolon after a `foreach`
# block.)
#
!!0,
);
}

# This is a workaround for a big bug in Parse::Keyword!
# The coderefs it returns get bound to lexical
# variables at compile-time. However, we need access
# to the variables at run-time.
#
sub _fixup {

# This is the coderef generated by Parse::Keyword.
#
my $coderef = shift;

# Find out what variables it closed over. If it didn't
# close over any variables, then it's fine as it is,
# and we don't need to fix it.
#
my $closed_over = PadWalker::closed_over($coderef);
return $coderef unless keys %$closed_over;

# Otherwise we need to return a new coderef that
# grabs its caller's lexical variables at run-time,
# pumps them into the original coderef, and then
# calls the original coderef.
#
return sub {
my $caller_pad = PadWalker::peek_my(2);
my %vars = map +($_ => $caller_pad->{$_}), keys %$closed_over;
PadWalker::set_closed_over($coderef, \%vars);
goto $coderef;
};
}
};

use My::Print qw( myprint );

my $start = "[";
my $end = "]";

myprint "a", "b", "c";

myprint { $start . $_ . $end } "a", "b", "c";
这会生成以下输出:
a
b
c
[a]
[b]
[c]

关于perl - 采用可选 block 参数的子例程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26368555/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com