使用子原型,我们可以定义我们自己的看起来像map或grep的subs。也就是说,第一个coderef参数的语法比普通的匿名子语言短。例如:
sub thunked (&) { $_[0] }
my $val = thunked { 2 * 4 };
这里工作得很好,因为第一个参数是coderef。但是对于后面的论点,它很简单,不能正确解析。
我创建了一个with
子程序,旨在使GTK2代码更清晰。它的意思是这样的(未经测试,因为它是假设的代码):
use 5.012;
use warnings;
use Gtk2 '-init';
sub with ($&) {
local $_ = $_[0];
$_[1]->();
$_;
}
for (Gtk2::Window->new('toplevel')) {
$_->set_title('Test Application');
$_->add(with Gtk2::VBox->new {
my $box = $_;
$box->add(Gtk2::Button->new("Button $_")) for (1..4);
});
$_->show_all;
}
Gtk2->main;
它不起作用,因为with
需要将块作为第一个参数才能使用漂亮的语法。有没有办法把它拉下来?
答案 0 :(得分:6)
模块Devel::Declare包含以相对安全的方式扩展Perl语法的工具。
使用Devel :: Declare你会在with
标记上创建一个钩子,当它到达那个词时会停止解析器。从那里,您可以控制解析器,您可以提前读取,直到达到{
符号。此时,您已经拥有了需要使用的内容,因此您将其重写为有效的Perl,并将其传递回解析器。
With.pm
中的:
package With;
use warnings;
use strict;
use Devel::Declare;
sub import {
my $caller = caller;
Devel::Declare->setup_for (
$caller => {with => {const => \&parser}}
);
no strict 'refs';
*{$caller.'::with'} = sub ($&) {
$_[1]() for $_[0];
$_[0]
}
}
our $prefix = '';
sub get {substr Devel::Declare::get_linestr, length $prefix}
sub set { Devel::Declare::set_linestr $prefix . $_[0]}
sub parser {
local $prefix = substr get, 0, length($_[0]) + $_[1];
my $with = strip_with();
strip_space();
set "scalar($with), sub " . get;
}
sub strip_space {
my $skip = Devel::Declare::toke_skipspace length $prefix;
set substr get, $skip;
}
sub strip_with {
strip_space;
my $with;
until (get =~ /^\{/) {
(my $line = get) =~ s/^([^{]+)//;
$with .= $1;
set $line;
strip_space;
}
$with =~ s/\s+/ /g;
$with
}
并使用它:
use With;
sub Window::add {say "window add: ", $_[1]->str}
sub Window::new {bless [] => 'Window'}
sub Box::new {bless [] => 'Box'}
sub Box::add {push @{$_[0]}, @_[1..$#_]}
sub Box::str {"Box(@{$_[0]})"}
sub Button::new {"Button($_[1])"}
with Window->new {
$_->add(with Box->new {
for my $num (1 .. 4) {
$_->add(Button->new($num))
}
})
};
打印哪些:
window add: Box(Button(1) Button(2) Button(3) Button(4))
完全不同的方法是完全跳过with
关键字并编写例程来生成构造函数子例程:
BEGIN {
for my $name (qw(VBox)) { # and any others you want
no strict 'refs';
*$name = sub (&@) {
use strict;
my $code = shift;
my $with = "Gtk2::$name"->new(@_);
$code->() for $with;
$with
}
}
}
然后你的代码看起来像
for (Gtk2::Window->new('toplevel')) {
$_->set_title('Test Application');
$_->add(VBox {
my $box = $_;
$box->add(Gtk2::Button->new("Button $_")) for (1..4);
});
$_->show_all;
}
答案 1 :(得分:5)
您可以处理的一种方法是添加一个相当无用的关键字:
sub perform(&) { $_[0] }
with GTK2::VBox->new, perform { ... }
其中perform
实际上只是sub
的替代品。
另一种方法是编写Devel::Declare过滤器或Syntax::Keyword:: plugin来实现with
,只要您有一些方法可以告诉您何时完成解析{{1}参数并准备好开始解析块 - 平衡括号会这样做(所以一个开放的大括号,但随后哈希成为一个问题)。然后你可以支持像
with
让过滤器将其重写为
with (GTK2::VBox->new) { ... }
如果有效,则具有不实际创建子的优点,因此不会干扰do {
local $_ = GTK2::VBox->new;
do {
...;
};
$_;
}
,@_
和其他一些事情。我认为这两层return
- 对于能够在适当的位置安装EndOfScope挂钩是必要的。
这明显的缺点是它很棘手,它很毛茸茸,而且它是一个源过滤器(即使它是一个温顺的过滤器),这意味着如果你想让任何使用它的代码可以调试,你必须要解决的问题