【问题标题】:Perl: Syntactical Sugar for Latter Coderef Arguments?Perl:后 Coderef 参数的语法糖?
【发布时间】:2012-01-27 00:49:09
【问题描述】:

使用 sub 原型,我们可以定义我们自己的 subs,看起来像 map 或 grep。也就是说,第一个 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 需要将块作为第一个参数才能使漂亮的语法起作用。有什么办法可以解决吗?

【问题讨论】:

    标签: perl syntax programming-languages lambda closures


    【解决方案1】:

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

    哪些打印:

    窗口添加:框(按钮(1)按钮(2)按钮(3)按钮(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;
    }
    

    【讨论】:

      【解决方案2】:

      您可以处理它的一种方法是添加一个相当无用的关键字:

      sub perform(&) { $_[0] }
      
      with GTK2::VBox->new, perform { ... }
      

      perform 实际上只是 sub 的一个更甜的替代品。

      另一种方法是编写Devel::Declare 过滤器或Syntax::Keyword:: plugin 来实现您的with,只要您有某种方法可以告诉您何时解析完with 参数并准备开始解析块 - 平衡括号会做(左花括号也可以,但随后哈希成为问题)。然后你可以支持类似的东西

      with (GTK2::VBox->new) { ... }
      

      并让过滤器将其重写为类似

      do {
          local $_ = GTK2::VBox->new;
          do {
              ...;
          };
          $_;
      }
      

      如果可行,它的优点是不会实际创建子,因此不会干扰@_return 和其他一些东西。我认为两层do-age 对于能够在适当的位置安装EndOfScope 挂钩是必要的。

      这样做的明显缺点是它很棘手,它很麻烦,而且它是一个源过滤器(即使它是一个驯服的过滤器),这意味着如果你想让任何使用它的代码完全可调试,你必须解决一些问题.

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-10-19
        • 2014-08-10
        • 1970-01-01
        • 2011-05-09
        • 1970-01-01
        • 2019-05-07
        • 2017-12-21
        相关资源
        最近更新 更多