是否有某种方法可以使诸如$ a和$ b这样的变量符合严格条件?

时间:2020-03-06 14:53:39  来源:igfitidea点击:

根据迈克尔·卡曼(Michael Carman)的评论,我决定重写这个问题。请注意,在此编辑之前出现了11条评论,并证实了迈克尔的观察,即我并未以清楚说明自己的方式写问题。
问题:通过简单地导入模块来伪造$ a和$ b相对于strict的特殊状态的标准或者最简洁的方法是什么?

首先进行一些设置。以下作品:

#!/bin/perl
use strict;
print "$a=$a\n";
print "$b=$b\n";

如果我再添加一行:

print "$c=$c\n";

编译时出现错误,这意味着我所有令人眼花print乱的打印代码都无法运行。

如果我注释掉" use strict;",它将运行良好。在没有限制的情况下,$ a和$ b主要是特殊的,因为sort将两个值与这些名称进行比较。

my @reverse_order = sort { $b <=> $a } @unsorted;

因此,关于$ a和$ b的主要功能差异(即使Perl"知道它们的名称")是我们在排序或者使用List :: Util中的某些功能时最好知道这一点。 。

只有当我们使用strict时,$ a$ b才以全新的方式成为特殊变量。它们是strict会传递的唯一变量,而不会抱怨没有声明它们。

:现在,我喜欢严格,但是让我吃惊的是,如果TIMTOWTDI(有多种方法可以做到)是Perl中的规则1,那不是TIMTOWDI。它说$ a$ b很特殊。如果我们想使用变量,则不必声明$ a和$ b。如果我们想通过添加$ c来拥有三个变量,突然之间就有了另一种完整的方法。

没关系,在处理散列$ k$ v可能更有意义:

my %starts_upper_1_to_25 
    = skim { $k =~ m/^\p{IsUpper}/ && ( 1 <= $v && $v <= 25 ) } %my_hash
;`

现在,我使用并且我喜欢严格。但是我只想让skip可以看到$ k和$ v,以获得最简洁的语法。我希望可以简单地看到它

use Hash::Helper qw<skim>;

我不是在问这个问题,不知道该怎么做。我在下面的"答案"应该让我们知道,我对Perl足够了解,因此很危险。我在问是否有一种方法可以使严格接受其他变量,或者什么是最干净的解决方案。答案很可能不是。如果真是这样,它似乎根本就不是TIMTOWTDI。

解决方案

$ a和$ b只是全局变量。我们只需声明$ k和$ v即可达到类似的效果:

use strict;
our ($k, $v);

(在这种情况下,$ k和$ v不是全局变量,而是包变量的词法范围别名。但是,如果我们不跨越边界,它们也同样足够。)

如果我理解正确,那么我们想要的是:

use vars qw($a $b); # Pre-5.6

或者

our ($a, $b); # 5.6 +

你可以在这里读到它。

其他人提到了如何"使用vars"和"我们的",我只是想补充一下$ a和$ b是特殊情况,因为它们在sort例程内部使用。这是strict.pm文档中的注释:

Because of their special use by sort(), the variables $a and $b are 
exempted from this check.

在Perl 5.6和更高版本中,我们可以使用我们的:

our ($k, $v);

或者,我们可以使用较旧的" use vars":

use vars qw($k $v);

或者我们可能只是坚持使用"我的",例如:

my %hash;
my ($k,$v);
while (<>) {
  /^KEY=(.*)/ and $k =  and next;
  /^VALUE=(.*)/ and $v = ;
  $hash{$k} = $v;
  print "$k $v\n";
}

__END__
KEY=a
VALUE=1
KEY=b
VALUE=2

在上面的示例中,确实没有必要创建全局$ v,但是希望我们能理解(另一方面,$ k的作用域需要在while块之外)。

另外,我们可以使用完全限定的变量名称:

$main::k="foo";
$main::v="bar";
%main::hash{$k}=$v;

编辑这实际上是不正确的,请参阅注释。把它留在这里给其他人一个学习我的错误的机会:)

哦,我们在问,是否有一种方法可以让模块在CALLER的命名空间中声明$ k和$ v?我们可以使用Exporter将变量上推到调用者:

use strict;

package Test; 
use Exporter;

my @ISA = qw/Exporter/; 
my $c = 3; 
my @EXPORT = qw/$c/; 

package main; 
print $c;

$ a和$ b是特殊的,因为它们是核心语言的一部分。虽然我可以理解为什么我们可能会说无法创建自己的类似特殊变量是反TIMTOWTDI,但我要说的不只是不能按'print'或者'种类'。 (我们可以在模块中定义子元素,但这不能使它们成为真正的关键字。这相当于使用"我们的$ k",我们似乎在说这不能像$ a那样使$ k变得足够。)

要将名称推送到其他人的名称空间中,这应该是Exporter的有效示例:

package SpecialK;

use strict;

use base 'Exporter';
BEGIN {
  our @EXPORT = qw( $k );
}

our $k;

1;

将其保存到SpecialK.pm,然后"使用SpecialK"应为我们提供$ k。请注意,只能导出"我们的"变量,而不能导出" my"。

听起来好像我们想做List :: MoreUtils`所做的那种魔术:

use strict;
my @a = (1, 2);
my @b = (3, 4);
my @x = pairwise { $a + $b } @a, @b;

我建议仅查看List :: MoreUtils源中的pairwise子项。它使用一些巧妙的符号表来将$ a$ b注入调用者的命名空间,然后将它们本地化到子正文中。我认为。

这为我工作:

package Special;
use base qw<Exporter>;
# use staging; -> commented out, my module for development
our $c;

our @EXPORT = qw<manip_c>;

sub import { 
    *{caller().'::c'} = *c;
    my $import_sub    = Exporter->can( 'import' );
    goto &$import_sub;
 }

它也通过严格传递$ c。

package main;
use feature 'say';
use strict;
use Special;
use strict;
say "In main: $c=$c";

manip_c( 'f', sub {
    say "In anon sub: $c=$c\n"; # In anon sub: $c=f
});

say "In main: $c=$c";

是的,我在模块中加上"使用严格"一词是很愚蠢的,但是我不知道内部原理,这可以解决潜在的排序问题。

这是你的追求吗?.....

use strict;
use warnings;
use feature qw/say/;

sub hash_baz (&@) {
    my $code   = shift;  
    my $caller = caller;
    my %hash   = (); 
    use vars qw($k $v);

    no strict 'refs';
    local *{ $caller . '::k' } = \my $k;
    local *{ $caller . '::v' } = \my $v;

    while ( @_ ) {
        $k = shift;
        $v = shift;
        $hash{ $k } = $code->() || $v;
    }

    return %hash;
}

my %hash = ( 
    blue_cat   => 'blue', 
    purple_dog => 'purple', 
    ginger_cat => 'ginger', 
    purple_cat => 'purple' );

my %new_hash = hash_baz { uc $v if $k =~ m/purple/ } %hash;

say "@{[ %new_hash ]}";

# =>  purple_dog PURPLE ginger_cat ginger purple_cat PURPLE blue_cat blue

我不确定是否有人对此进行了澄清,但是严格的不会将$ a和$ b列入白名单,只是因为它们确实是方便使用的变量名,我们可以在自己的例程中使用它们。 $ a和$ b对sort运算符有特殊含义。从这种排序例程的角度来看,这是好的,但从外部来看,这是一种不好的设计。 :)如果我们正在使用其他环境,则不应使用$ a和$ b。

如果我理解问题,我们想编写一个模块,该模块在用户的命名空间中声明变量(这样就不必这样做),并且该模块会在回调中自动进行本地化。是对的吗?

我们可以通过声明全局变量并导出它们来实现。 (不过请注意,通常不要求出口这种东西就被认为是不好的形式。)

package Foo;
use strict;
use warnings;

require Exporter;
our @ISA    = qw(Exporter);
our @EXPORT = qw(*k *v hashmap);
our ($k, $v);

sub hashmap(&\%) {
    my $code = shift;
    my $hash = shift;

    while (local ($k, $v) = each %$hash) {
        $code->();
    }
}

注意:导出为* k和* v,而不是$ k和$ v。如果不导出整个typeglob,则" hashmap"中的" local"将无法从用户包中正常工作。这样做的副作用是所有声明的k和v形式(%k,@ v等)都被声明和别名。有关此内容的完整说明,请参见perlmod中的符号表。

然后在脚本中:

use Foo; # exports $k and $v

my %h = (a => 1, b => 2, c => 3);

hashmap { print "$k => $v\n" } %h;

__END__
c => 3
a => 1
b => 2

但是,$ a和$ b不是普通变量,并且不能通过词汇声明或者显式导出或者弄乱符号表来轻松地复制$ a和$ b。例如,使用调试器作为外壳程序:

DB<1> @foo = sort { $b cmp $a } qw(foo bar baz wibble);

  DB<2> x @foo
0  'wibble'
1  'foo'
2  'baz'
3  'bar'
 DB<3> x $a
0  undef
  DB<4> x $b
0  undef

$ a和$ b仅存在于传递给sort()的块中,此后不存在,并且具有这样的作用域,使得对sort的任何其他调用都不会在它们之上出现。

要复制它,我们可能需要开始弄乱源过滤器,以转换首选表示法

my %starts_upper_1_to_25 
    = skim { $k =~ m/^\p{IsUpper}/ && ( 1 <= $v && $v <= 25 ) } %my_hash
;

有效地

my %starts_upper_1_to_25
    = map { my $k = $_; my $v = $my_hash{$v};
            $k =~ m/^\p{IsUpper}/ && ( 1 <= $v && $v <=> 25 ) } keys %my_hash
;

$ a和$ b与$ _和@_一样特殊,虽然在Perl 5中没有简单的方法来更改这些名称,但是Perl 6确实使用给定的关键字解决了这个问题。 "给定"是一个令人讨厌的术语,但是http://dev.perl.org/perl6/doc/design/syn/S03.html可能是一个不错的起点。

这些模块表明use export实际上与use vars没有什么不同。
但是在每个使用$ a-like变量的软件包中都需要使用use vars
而且我们的" our()"将需要在每个外部范围内完成。

请注意,即使使用$ -prototyped子程序,也可以避免使用$ a和$ b进行排序:

sub lccmp($$) { lc($_[0]) cmp lc($_[1]) }
print join ' ', sort lccmp
   qw/I met this guy and he looked like he might have been a hat-check clerk/;

在与sort调用不同的包中使用比较例程时,这是必不可少的。