11

有没有人为 Perl 中的惰性求值列表找到了一个好的解决方案?我尝试了很多方法来改变类似的东西

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

进入懒惰的评估——例如,通过绑定@list。我试图避免分解并编写源过滤器来做到这一点,因为它们会干扰您调试代码的能力。有没有人成功过。还是您只需要分解并使用while循环?

注意:我想我应该提一下,有时我有点迷上了用于功能转换列表的长 grep-map 链。所以与其说是 foreach 循环或 while 循环。这是地图表达式倾向于将更多功能打包到相同的垂直空间中。

4

8 回答 8

13

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

为简单起见,我建议使用迭代器对象或闭包,而不是尝试使用延迟评估的数组。虽然您可以使用 tie 来获得一个惰性求值的无限列表,但如果您(直接或间接地,如上面的 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模块,它为惰性数组提供了更丰富、更完整的接口。我自己没有使用过该模块,因此您的里程可能会有所不同。

祝一切顺利,

保罗

于 2008-09-21T01:16:22.580 回答
9

[旁注:请注意,沿 map/grep 链的每一步都是急切的。如果你一次给它一个很大的列表,你的问题比决赛要早得多foreach。]

为了避免完全重写,你可以做的是用一个外循环包裹你的循环。而不是这样写:

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

……这样写:

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

这使您不必大量重写现有代码,并且只要列表在转换过程中不增长几个数量级,您几乎可以获得重写迭代器样式所提供的所有好处。

于 2008-09-21T17:45:11.907 回答
7

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

祝你好运, :)

于 2008-09-21T18:30:18.757 回答
5

至少存在一种特殊情况,其中 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 的优秀书籍Higher Order Perl可以引导您了解这些技术。但是请注意,它们将对您的代码进行更大的更改。

于 2008-09-21T01:09:58.787 回答
4

把这个从死里复活,提到我刚刚List::GenCPAN上写了这个模块,这正是海报所寻找的:

use List::Gen;

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

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

每个函数都返回一个“生成器”,它是对绑定数组的引用。您可以直接使用绑定数组,也可以使用一堆访问器方法,如迭代器。

于 2010-01-25T22:14:40.603 回答
3

使用迭代器或考虑使用CPAN 中的Tie::LazyList(有点过时)。

于 2008-09-21T01:08:25.127 回答
3

我在 perlmonks.org 上问了一个类似的问题,BrowserUk在他的回答中给出了一个非常好的框架。基本上,获得惰性评估的一种方便方法是为计算生成线程,至少只要您确定想要结果,就不是现在。如果您希望惰性评估不是为了减少延迟而是为了避免计算,我的方法将无济于事,因为它依赖于推送模型,而不是拉取模型。可能使用Coro程序,您也可以将这种方法转换为(单线程)拉模型。

map在思考这个问题的同时,我还研究了将数组绑定到线程结果以使 Perl 程序流程parallel更像结果的方法。更多文档版本的代码将作为对该线程的回复发布,也可能发布到 CPAN 上。

于 2008-09-22T08:42:49.907 回答
2

如果我没记错的话,for/foreach 无论如何都会首先获取整个列表,因此将完全读取一个懒惰评估的列表,然后它会开始遍历元素。因此,我认为除了使用 while 循环之外别无他法。但我可能错了。

while 循环的优点是您可以使用代码引用来伪造延迟评估列表的感觉:

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

毕竟,我想在 Perl 5 中,平局是最接近的。

于 2008-09-21T00:56:39.163 回答