Perl + 递归子例程 + 访问子例程外定义的变量

Perl + recursive subroutine + accessing variable defined outside of subroutine

我正在使用 Perl 提取 bitbucket 回购列表。来自 bitbucket 的响应将仅包含 10 个存储库和下一页的标记,下一页将有另外 10 个存储库等等......(他们称之为分页响应)

因此,我编写了一个递归子例程,如果存在下一页标记,它会自行调用。这将一直持续到最后一页。

这是我的代码:

#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
use LWP::UserAgent;
use JSON;

my @array;

recursive("my_bitbucket_url");
foreach ( @array ) { print $_."\n"; }

sub recursive
{
    my $url    = $_[0];

    ### here goes my LWP::UserAgent code which connects to bitbucket and pulls back the response in a JSON as $response->decoded_content 
    ### hence, removing this code for brevity

    my $hash = decode_json $response->decoded_content;
    #print Dumper ($hash);

    foreach my $a ( @{$hash->{values}} )
    {
        push @array, $a->{links}->{self}->{href};
    }

    if ( defined $hash->{next})
    {
        print "Next page Exists \n";
        print "Recursing with $hash->{next} \n";
        recursive( $hash->{next} );
    }
    else
    {
        print "Last page reached. No more recursion \n"
    }
}

现在,我的代码工作正常,它列出了所有的存储库。

问题: 我不确定我在上面使用变量 my @array; 的方式。我已经在子程序之外定义了它,但是,我直接从子程序访问它。不知何故,我觉得这是不对的。

那么,在这种情况下如何使用递归子例程追加到数组。我的代码是否遵守 Perl 道德规范,还是真的很荒谬(但正确,因为它有效)?

更新

在遵循@ikegami、@Sobrique 和@Hynek -Pichi- Vychodil 的建议后,我提供了以下使用 while 循环并避免重复的代码。

这是我的思考过程:

  1. 定义数组@array.
  2. 使用初始 bitbucket URL 调用子例程 call_url 并将响应保存在 $hash
  3. 检查 $hash 下一个 页面标记
    • 如果 下一个 页面标记存在,则将元素推送到 @array 并使用新标记调用 call_url。这将通过 while 循环完成。
    • 如果 下一个 页面标记 存在,则将元素推送到 @array。期间.
  4. 打印@array内容。

这是我的代码:

my @array;
my $hash = call_url("my_bitbucket_url ");

if (defined $hash->{next})
{
    while (defined $hash->{next})
    {
        foreach my $a ( @{$hash->{values}} )
        {
            push @array, $a->{links}->{self}->{href};
        }
        $hash = call_url($hash->{next});
    }
}

foreach my $a ( @{$hash->{values}} )
{
    push @array, $a->{links}->{self}->{href};
}

foreach (@array) { print $_."\n"; }

sub call_url
{
    ### here goes my LWP::UserAgent code which connects to bitbucket and pulls back the response in a JSON as $response->decoded_content 
    ### hence, removing this code for brevity    

    my $hash = decode_json $response->decoded_content;
    #print Dumper ($hash);

    return $hash;
}

很想知道这看起来是否还可以,或者还有改进的余地。

在任何闭包之外定义的变量可用于整个程序。它工作正常,没有什么可担心的。有些人可能会在某些情况下将其称为 'bad style'(主要是围绕程序长度和距离动作),但这并不是硬性限制。

不过,我不确定我是否一定能在这里看到递归的优势 - 您的问题似乎不成立。这本身不是问题,但对于未来的维护程序员来说可能有点混乱;)。

我会按照以下方式思考(非递归):

my $url = "my_bitbucket_url";

while ( defined $url ) {
    ##LWP Stuff;

    my $hash = decode_json $response->decoded_content;

    foreach my $element ( @{ $hash->{values} } ) {
        print join( "\n", @{ $element->{links}->{self}->{href} } ), "\n";
    }

    $url = $hash->{next}; #undef if it doesn't exist, so loop breaks. 
}

将全局变量用于 return 值演示了 high coupling,需要避免的事情。

你问的是以下是否可以接受:

my $sum;
sum(4, 5);
print("$sum\n");
sub sum {
   my ($x, $y) = @_;
   $sum = $x + $y;
}

sub 是递归的事实完全无关紧要;它只会让你的例子更大。


问题已解决:

sub recursive
{
    my $url = $_[0];

    my @array;

    my $hash = ...;

    foreach my $a ( @{$hash->{values}} )
    {
        push @array, $a->{links}->{self}->{href};
    }

    if ( defined $hash->{next})
    {
        print "Next page Exists \n";
        print "Recursing with $hash->{next} \n";
        push @array, recursive( $hash->{next} );
    }
    else
    {
        print "Last page reached. No more recursion \n"
    }

    return @array;
}

{
    my @array = recursive("my_bitbucket_url");
    foreach ( @array ) { print $_."\n"; }
}

移除递归:

sub recursive
{
    my $url = $_[0];

    my @array;
    while (defined($url)) {    
        my $hash = ...;

        foreach my $a ( @{$hash->{values}} )
        {
            push @array, $a->{links}->{self}->{href};
        }

        $url = $hash->{next};

        if ( defined $url)
        {
            print "Next page Exists \n";
            print "Recursing with $url\n";
        }
        else
        {
            print "Last page reached. No more recursion \n"
        }
    }

    return @array;
}

{
    my @array = recursive("my_bitbucket_url");
    foreach ( @array ) { print $_."\n"; }
}

清理您发布的最新代码:

my $url = "my_bitbucket_url";

my @array;
while ($url) {
    my $hash = call_url($url);

    for my $value ( @{ $hash->{values} } ) {
       push @array, $value->{links}{self}{href};
    }

    $url = $hash->{next};
}

print("$_\n") for @array;

是的,即使是词法范围变量,使用全局变量也是坏习惯。

每个递归代码都可以重写为它的命令式循环版本,反之亦然。这是因为所有这些都是在 CPU 上实现的,它对递归一无所知。只有跳跃。所有调用和 returns 都只是一些堆栈操作的跳转,因此您可以将递归算法重写为循环。如果它不像本例那样明显和简单,您甚至可以像在您最喜欢的语言解释器或编译器中所做的那样模拟堆栈和行为。在这种情况下,它非常简单:

my @array = with_loop("my_bitbucket_url");
foreach ( @array ) { print $_."\n"; }

sub with_loop
{
    my $url    = $_[0];
    my @array;
    while(1) 
    {

    ### here goes my LWP::UserAgent code which connects to bitbucket and 
    ### pulls back the response in a JSON as $response->decoded_content 
    ### hence, removing this code for brevity

        my $hash = decode_json $response->decoded_content;
        #print Dumper ($hash);

        foreach my $a ( @{$hash->{values}} )
        {
            push @array, $a->{links}->{self}->{href};
        }

        unless ( defined $hash->{next})
        {
            print "Last page reached. No more recursion \n";
            last
        };

        print "Next page Exists \n";
        print "Recursing with $hash->{next} \n";
        $url = $hash->{next};
    };
    return @array;
}

但是当你想坚持递归时你可以,但它有点棘手。首先,没有尾调用优化,因此您不必像原始版本那样尝试编写尾调用代码。所以你可以这样做:

my @array = recursion("my_bitbucket_url");
foreach ( @array ) { print $_."\n"; }

sub recursion
{
    my $url    = $_[0];

    ### here goes my LWP::UserAgent code which connects to bitbucket and 
    ### pulls back the response in a JSON as $response->decoded_content 
    ### hence, removing this code for brevity

    my $hash = decode_json $response->decoded_content;
    #print Dumper ($hash);

    # this map version is same as foreach with push but more perlish
    my @array = map $_->{links}->{self}->{href}, @{$hash->{values}};

    if (defined $hash->{next})
    {
        print "Next page Exists \n";
        print "Recursing with $hash->{next} \n";
        push @array, recursive( $hash->{next} );
    }
    else
    {
        print "Last page reached. No more recursion \n"
    }
    return @array;
}

但是这个版本效率不是很高,所以有办法在 perl 中编写尾调用递归版本,这有点棘手。

my @array = tail_recursive("my_bitbucket_url");
foreach ( @array ) { print $_."\n"; }

sub tail_recursive
{
    my $url    = $_[0];
    my @array;
    return tail_recursive_inner($url, \@array); 
    # url is mutable parameter
}

sub tail_recursive_inner
{
    my $url = $_[0];
    my $array = $_[1]; 
    # $array is reference to accumulator @array 
    # from tail_recursive function

   ### here goes my LWP::UserAgent code which connects to bitbucket and 
   ### pulls back the response in a JSON as $response->decoded_content 
   ### hence, removing this code for brevity

    my $hash = decode_json $response->decoded_content;
    #print Dumper ($hash);

    foreach my $a ( @{$hash->{values}} )
    {
        push @$array, $a->{links}->{self}->{href};
    }

    if (defined $hash->{next})
    {
        print "Next page Exists \n";
        print "Recursing with $hash->{next} \n";

        # first parameter is mutable so its OK to assign
        $_[0] = $hash->{next};
        goto &tail_recursive_inner;
    }
    else
    {
        print "Last page reached. No more recursion \n"
    }
    return @$array;
}

如果您对一些真正的 perl 技巧感兴趣

print $_."\n" for tricky_tail_recursion("my_bitbucket_url");

sub tricky_tail_recursion {
    my $url = shift;

   ### here goes my LWP::UserAgent code which connects to bitbucket and 
   ### pulls back the response in a JSON as $response->decoded_content 
   ### hence, removing this code for brevity

    my $hash = decode_json $response->decoded_content;
    #print Dumper ($hash);

    push @_, $_->{links}->{self}->{href} for @{$hash->{values}}; 

    if (defined $hash->{next}) {
        print "Next page Exists \n";
        print "Recursing with $hash->{next} \n";
        unshift @_, $hash->{next};
        goto &tricky_tail_recursion;
    } else {
        print "Last page reached. No more recursion \n"
    };
    return @_;
}

另请参阅:LWP::UserAgent 文档。