简体   繁体   English

尾调用递归“优化”

[英]Tail call Recursion “Optimising”

I have a weird problem I can't figure out. 我有一个奇怪的问题,我无法解决。 I created a simple sequence in Perl with anonymous functions. 我在Perl中使用匿名函数创建了一个简单序列。

sub{($data, sub{($data, sub{($data, sub{($data, empty)})})})};

And it works but I tired to implement tail optimizing and got some weird behaviour. 它可以工作,但是我厌倦了执行尾部优化并得到一些奇怪的行为。 Example. 例。 The iter function below works. 下面的iter函数起作用。

sub iter {
    my ($func, $seq) = @_;
    my ($data, $next) = $seq->();

    if (defined $data) {
        $func->($data);
        @_ = ($func, $next);#This @_ update works fine
        goto &iter;
    }
}

while this implementation of iter fails. 但是iter的此实现失败。

sub iter {
    my ($func, $seq) = @_;
    my ($data, $next) = $seq->();

    if (defined $data) {
        $func->($data);
        $_[1] = $next; #This @_ update fails
        goto &iter;
    }
}

Both updates of @_ yield the same values for @_ but the code behaves differently when it continues.. To see what I'm talking about try running the complete code below. @_的两个更新都会为@_产生相同的值,但是当代码继续执行时,代码的行为将有所不同。要了解我在说什么,请尝试运行下面的完整代码。

#! /usr/bin/env perl

package Seq;

use 5.006;
use strict;
use warnings;

sub empty {
    sub{undef};
}

sub add {
    my ($data, $seq) = @_;
    sub{($data, $seq)};
}

sub iter {
    my ($func, $seq) = @_;
    my ($data, $next) = $seq->();

    if (defined $data) {
        $func->($data);
        @_ = ($func, $next);#This works fine
        #$_[1] = $next; #This fails
        goto &iter;
    }
}

sub smap {
    my ($func, $seq) = @_;
    my ($data, $next) = $seq->();
    if (defined $data) {
        sub{($func->($data), Seq::smap($func, $next))};
    }else {
        empty();
    }
}

sub fold {
    my ($func, $acc, $seq) = @_;
    my ($data, $next) = $seq->();
    if (defined $data) {
        @_ = ($func, $func->($acc, $data), $next);
        goto &Seq::fold;
    }else {
        $acc;
    }
}

1;

package main;

use warnings;
use strict;
use utf8;

use List::Util qw(reduce);

my $seq =
    reduce
    {Seq::add($b, $a)}
    Seq::empty,
    (4143, 1234, 4321, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

Seq::iter(sub{my ($data) = @_; STDOUT->print("$data\n")}, $seq);

my $seq2 = Seq::smap(sub{my ($data) = @_; $data * 2}, $seq);

STDOUT->print("\n\n");

Seq::iter(sub{my ($data) = @_; STDOUT->print("$data\n")}, $seq2);

STDOUT->print("\n\n");

my $ans = Seq::fold(sub{my ($acc, $data) = @_; $acc + $data}, 0, $seq);
my $ans2 = Seq::fold(sub{my ($acc, $data) = @_; $acc + $data}, 0, $seq2);

STDOUT->print("$ans\n");
STDOUT->print("$ans2\n");

exit (0);

The code should work for both examples of iter but it doesn't.. Any pointers why? 该代码应适用于iter的两个示例,但不能。.任何指针都为什么?

Writing to $_[1] writes to the second scalar passed to the sub. 写入$_[1]将写入传递给子程序的第二个标量。

$ perl -E'$x = "abc"; say $x; sub { $_[0] = "def"; say $_[0]; }->($x); say $x;'
abc
def
def

So you are clobbering the caller's variables. 因此,您正在破坏调用方的变量。 Assigning to @_ replaces the scalars it contains rather than writing to them. 分配给@_替换它包含的标量,而不是写入它们。

$ perl -E'$x = "abc"; say $x; sub { @_ = "def"; say $_[0]; }->($x); say $x;'
abc
def
abc

You can replace a specific element using splice . 您可以使用splice替换特定元素。

$ perl -E'$x = "abc"; say $x; sub { splice(@_, 0, 1, "def"); say $_[0]; }->($x); say $x;'
abc
def
abc

It's far more convenient for iterators to return an empty list when they are exhausted. 对于迭代器,当它们用尽时返回一个空列表要方便得多。 For starters, it allows them to return undef . 对于初学者,它允许他们返回undef

Furthermore, I'd remove the expensive recursive calls with quicker loops. 此外,我将通过更快的循环来消除昂贵的递归调用。 These loops can be made particularly simple because of the change mentioned above. 由于上述更改,可以使这些循环特别简单。

The module becomes: 该模块将变为:

package Seq;

use strict;
use warnings;

sub empty { sub { } }

sub add {
    my ($data, $seq) = @_;
    return sub { $data, $seq };
}

sub iter {
    my ($func, $seq) = @_;
    while ( (my $data, $seq) = $seq->() ) {
        $func->($data);
    }
}

sub smap {
    my ($func, $seq) = @_;
    if ( (my $data, $seq) = $seq->() ) {
        return sub { $func->($data), smap($func, $seq) };
    } else {
        return sub { };
    }
}

sub fold {
    my ($func, $acc, $seq) = @_;
    while ( (my $data, $seq) = $seq->() ) {
        $acc = $func->($acc, $data);
    }

    return $acc;
}

1;

Also, for speed reasons, replace 另外,出于速度原因,请更换

sub { my ($data) = @_; $data * 2 }
sub { my ($acc, $data) = @_; $acc + $data }

with

sub { $_[0] * 2 }
sub { $_[0] + $_[1] }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM