【问题标题】:Perl variable scope questionPerl 变量范围问题
【发布时间】:2010-11-22 11:04:06
【问题描述】:

所以我有一个 Perl 类。它有一个sort() 方法,我希望它或多或少与内置的sort() 函数相同:

$object->sort(sub ($$) { $_[0] <=> $_[1] });

但我做不到:

$object->sort(sub { $a <=> $b });

因为范围。但是 List::Util 模块使用reduce() 执行此操作。我查看了 List::Util 模块,他们使用no strict 'vars' 做了一些相当讨厌的事情来实现这一点。我试过了,但没有用。

据我了解,reduce() 以它的方式工作,因为它被导出到适当的命名空间中,因此我的类不能这样做,因为该函数非常牢固地位于另一个命名空间中。这是正确的,还是在我的情况下有一些(无疑更可怕和不明智的)方法可以做到这一点?

【问题讨论】:

    标签: perl class scope


    【解决方案1】:

    嗯,其他两个答案都对了一半。这是一个实际排序的有效解决方案:

    package Foo;
    
    use strict;
    use warnings;
    
    sub sort {
        my ($self, $sub) = @_;
    
        my ($pkg) = caller;
        my @x = qw(1 6 39 2 5);
        print "@x\n";
        {
            no strict 'refs';
            @x = sort {
                local (${$pkg.'::a'}, ${$pkg.'::b'}) = ($a, $b);
                $sub->();
            } @x;
        }
        print "@x\n";
    
        return;
    }
    
    
    package main;
    
    use strict;
    use warnings;
    
    my $foo = {};
    bless $foo, 'Foo';
    
    $foo->sort(sub { $a <=> $b });
    # 1 6 39 2 5
    # 1 2 5 6 39
    

    大概你会对一些实际上是对象一部分的数据进行排序。

    您需要caller 魔法,以便在调用者的包中本地化$a$b,这是Perl 将在其中寻找它们的地方。它正在创建仅在调用 sub 时才存在的全局变量。

    请注意,warnings 将获得一个“仅使用一次的名称”;我敢肯定,您可以通过某种方式来避免这种情况。

    【讨论】:

    • 这对于您的目的可能已经足够了,但它很脆弱。不能保证比较函数与sort 方法的调用者属于同一个包。这就是 Sub::Identify 的用武之地。
    • @cjm - 这是真的,我肯定会研究 Sub::Identify,但我更大的问题是让它工作,而不是让它在一般情况下工作。具体解决方案优于一般故障。但是,将此答案与您的答案结合起来会给我一个通用的解决方案,这是一件好事。
    • 虽然事实证明 sort 内置有同样的问题。它假定比较函数来自与调用者相同的包。因此,如果您可以忍受,您可以保存对 Sub::Identify 的依赖。 (或者您可以有条件地要求 Sub::Identify,如果未安装,则回退到 caller。但这需要更多工作。)
    • 其实这正是我一直在考虑的,我想我会去做的。
    • 啊哈,我从来没想过。虽然我不确定是否应该鼓励这样的事情。
    【解决方案2】:

    您可以使用Sub::Identify 找出与coderef 关联的包(它称为stash_name)。然后根据需要在该包中设置 $a 和 $b。您可能需要在您的方法中使用 no strict 'refs' 才能使其正常工作。

    这是修改后的 Evee 的答案以适用于一般情况:

    use strict;
    use warnings;
    
    package Foo;
    
    use Sub::Identify 'stash_name';
    
    sub sort {
        my ($self, $sub) = @_;
    
        my $pkg = stash_name($sub);
        my @x = qw(1 6 39 2 5);
        print "@x\n";
        {
            no strict 'refs';
            @x = sort {
                local (${$pkg.'::a'}, ${$pkg.'::b'}) = ($a, $b);
                $sub->();
            } @x;
        }
        print "@x\n";
    
        return;
    }
    
    
    package Sorter;
    
    sub compare { $a <=> $b }
    
    package main;
    
    use strict;
    use warnings;
    
    my $foo = {};
    bless $foo, 'Foo';
    
    $foo->sort(\&Sorter::compare );
    
    $foo->sort(sub { $b <=> $a });
    

    【讨论】:

    • List::Util 只使用caller。我想这还不够,在一般情况下,是吗?如果调用者从其他包传递一个函数,那么 List::Util 将设置调用者的$a 而不是函数的。
    • 5.10.1 中的 List::Util 版本使用 XS 代码,基本上与 Sub::Identify 做相同的事情来找出 coderef 所属的包。
    • 另外,我可以在 XS 中重写我的模块,从而失去依赖并且让我有机会学习 XS。不过,与此同时,我会调查一下。
    【解决方案3】:

    您可以使用the local operator 在子例程调用期间设置$a$b 的值:

    sub sample
    {
        my $callback = shift;
        for (my $i = 0; $i < @_; $i += 2) {
            local ($a, $b) = @_[$i, $i + 1];
            $callback->();
        }
    }       
    
    sample sub { print "$a $b\n" }, qw(a b c d e f g h i j);
    

    如果你有一个普通的子程序,而不是一个方法,那么你可以让它更像sort,所以你不需要在你的回调函数之前使用sub。在函数上使用原型:

    sub sample (&@)
    

    那你这样称呼它:

    sample { print "$a $b\n" } qw(a b c d e f g h i j);
    

    不过,方法不受原型的影响。

    【讨论】:

    • 他专门询问method,而不是普通的子。
    • 如果您从类外调用该方法,那将不起作用。您正在本地化 类的 $a 和 $b,而不是调用者的。
    • 啊。我以为我已经看到了这个,但我想没有。我对 perlvar 的印象是 $a$b 足够神奇,它们在这种情况下“正常工作”。
    • 不,$a 和 $b 只是普通的包变量。
    • @Rob:是的,我也是这么想的——但是当我检查时,perlvar 实际上确实说它们是包变量。
    猜你喜欢
    • 2016-08-30
    • 2011-11-21
    • 2011-04-13
    • 2011-10-20
    • 2016-06-21
    • 2011-04-10
    • 2011-08-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多