Perl 6的这一方面是否有Perl解决方案用于延迟列出?

时间:2020-03-06 14:29:50  来源:igfitidea点击:

是否有人在Perl中为延迟评估的列表找到了一个好的解决方案?我尝试了多种方法来改变

for my $item ( map { ... } @list ) { 
}

进入懒惰的评估-例如,通过绑定@list。我试图避免崩溃并编写源过滤器来执行此操作,因为它们会干扰我们调试代码的能力。有没有人有任何成功。还是只需要分解并使用while循环?

注意:我想我应该提到,我有时会迷上长的grep-map链来进行功能上的转换。因此,它不是foreach循环或者while循环。映射表达式倾向于将更多功能打包到相同的垂直空间中。

解决方案

如果我没记错的话,for / foreach还是会先获取整个列表,因此将完全读取惰性计算的列表,然后开始遍历元素。因此,我认为没有其他方法可以使用while循环。但是我可能是错的。

while循环的优点在于,我们可以使用代码引用来伪装懒惰求值列表的感觉:

my $list = sub { return calculate_next_element };
while(defined(my $element = &$list)) {
    ...
}

毕竟,我猜想在Perl 5中可以找到一条平手。

使用迭代器,或者考虑使用CPAN中的Tie :: LazyList(日期为tad)。

至少有一种特殊情况,其中for和foreach已优化为不立即生成整个列表。那就是范围运算符。因此,我们可以选择说:

for my $i (0..$#list) {
  my $item = some_function($list[$i]);
  ...
}

这将遍历数组,并按我们希望的方式进行转换,而无需预先创建一长串值。

如果希望map语句返回可变数量的元素,则可以执行以下操作:

for my $i (0..$#array) {
  for my $item (some_function($array[$i])) {
    ...
  }
}

如果我们希望比这更普遍的惰性,那么最好的选择是学习如何使用闭包来生成惰性列表。 MJD的出色著作《高阶Perl》可以带我们了解这些技术。但是请注意,它们将对代码进行更大的更改。

如前所述,for(each)是一个热切的循环,因此它想在开始之前评估整个列表。

为简单起见,我建议使用迭代器对象或者闭包,而不要尝试使用惰性计算的数组。虽然我们可以使用平局来懒惰地评估无限列表,但是如果我们(直接或者间接地,如上面的foreach所述)询问整个列表(甚至整个列表的大小),就会遇到麻烦。

无需编写完整的类或者使用任何模块,只需使用闭包即可创建简单的迭代器工厂:

sub make_iterator {
    my ($value, $max, $step) = @_;

    return sub {
        return if $value > $max;    # Return undef when we overflow max.

        my $current = $value;
        $value += $step;            # Increment value for next call.
        return $current;            # Return current iterator value.
    };
}

然后使用它:

# All the even numbers between 0 -  100.
my $evens = make_iterator(0, 100, 2);

while (defined( my $x = $evens->() ) ) {
    print "$x\n";
}

CPAN上还有Tie :: Array :: Lazy模块,它为惰性数组提供了更丰富,更完整的接口。我没有亲自使用该模块,因此里程可能会有所不同。

一切顺利,

保罗

[旁注:请注意,沿着地图/ grep链的每个步骤都很渴望。如果一次列出全部清单,那么问题的发生要比最后的" foreach"要早得多。

为避免完全重写,我们可以做的是用外部循环包装循环。而不是这样写:

for my $item ( map { ... } grep { ... } map { ... } @list ) { ... }

这样写:

while ( my $input = calculcate_next_element() ) {
    for my $item ( map { ... } grep { ... } map { ... } $input ) { ... }
}

这使我们不必大量重写现有代码,并且只要列表在转换过程中不会增长几个数量级,我们几乎就能获得重写为迭代器样式所带来的几乎所有好处。

如果要创建惰性列表,则必须编写自己的迭代器。一旦有了它,就可以使用Object :: Iterate之类的东西,它具有迭代器感知的map和grep版本。看一下该模块的源代码:它非常简单,我们将看到如何编写自己的支持迭代器的子例程。

祝你好运, :)

我在perlmonks.org上问了类似的问题,BrowserUk在回答中给出了一个非常好的框架。基本上,获得懒惰评估的一种便捷方法是生成线程用于计算,至少在我们确定想要结果的情况下," Just Not Now"。如果我们希望进行惰性评估而不是减少延迟但避免计算,那么我的方法将无济于事,因为它依赖于推模型而不是拉模型。可能使用Corooutines,我们也​​可以将此方法转换为(单线程)拉模型。

在思考这个问题的同时,我还研究了将数组与线程结果绑定的方法,以使Perl程序的流程更像" map",但是到目前为止,我还是喜欢引入" parallel"" keyword"(对象构造函数)的API。变相),然后对结果调用方法。该代码的更多文档版本将作为对该线程的答复而发布,并且还可能发布到CPAN上。

让我们从死灰复燃地回想起,我刚刚在CPAN上编写了模块List :: Gen,其功能完全符合发布者的期望:

use List::Gen;

for my $item ( @{gen { ... } \@list} ) {...}

列表的所有计算都是惰性的,并且具有map / grep等效项以及一些其他功能。

每个函数都返回一个"生成器",该生成器是对绑定数组的引用。我们可以直接使用绑定数组,也可以使用许多访问器方法(例如迭代器)。