Perl 6之前有没有关于惰性列表的Perl解决方案?

11

有没有人在Perl中找到了懒惰求值列表的好解决方案? 我尝试过许多方法来将类似于

转换成需要的格式,但是都没有成功。

Has anybody found a good solution for lazily-evaluated lists in Perl? I've tried a number of ways to turn something like

into the desired format, but without success.
for my $item ( map { ... } @list ) { 
}

我想把它转化为一种懒惰求值方式,例如通过绑定@list。我试图避免编写源代码过滤器来实现,因为它们会影响您调试代码的能力。有人成功了吗?还是只能放弃并使用while循环?

注意:我想提到的是,我有点着迷于有时很长的grep-map链,用于对列表进行函数变换。因此,并不是foreach循环或while循环的问题,而是map表达式往往在同样的垂直空间内打包更多的功能。

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模块,它提供了更丰富、更完整的延迟数组接口。我自己没有使用过这个模块,所以你的实际情况可能会有所不同。

祝一切顺利!

保罗


1
如果您想了解更多关于这种编程的内容,请阅读Mark Jason Dominus所著的《Higher Order Perl》一书。我认为是非常好的。 - moritz
2
在范围运算符的特殊情况下,for/foreach不会获取整个列表。 - user11318

9
[Sidenote: 请注意,map/grep链中的每个步骤都是急切计算的。如果一次性给它一个大列表,你的问题会比最后的foreach语句出现得早得多。]
为避免完全重写代码,您可以使用外部循环包装您的循环。不要写成这样:
for my $item ( map { ... } grep { ... } map { ... } @list ) { ... }

写成这样:

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

这样做可以避免你大量重写现有代码,只要在转换过程中列表不会增长数个数量级,你就能获得几乎与迭代器样式重写相同的效益。

7
如果你想创建懒惰列表,你需要编写自己的迭代器。一旦你拥有了它,就可以使用类似于 Object::Iterate 的东西,它具有迭代器感知版本的 mapgrep。查看该模块的源代码:它非常简单,你将学会如何编写自己的迭代器感知子例程。
祝好运,:)

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》可以引导你掌握这些技巧。但是请注意,它们将对你的代码产生很大的改变。

4

提起这个话题是为了提到我刚刚在CPAN上编写了一个名为List::Gen的模块,该模块正好可以满足发帖者的需求:

use List::Gen;

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

所有列表的计算都是惰性的,并且有map/grep等相应的函数。

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


没问题,如果你认为应该有任何功能,请告诉我。 - Eric Strom

3

3
我曾在perlmonks.org上提出了类似的问题,BrowserUk在他的答案中给出了一个非常好的框架。基本上,一种方便获取惰性计算的方法是为计算生成线程,至少在您确定要结果时是这样。 如果您想要惰性评估以避免计算而不是降低延迟,则我的方法无法帮助,因为它依赖于“推”模型,而不是“拉”模型。 可能使用Corooutines,您也可以将该方法转换为(单线程)拉模型。

在考虑此问题时,我还调查了将数组绑定到线程结果以使Perl程序流程更像map的方法,但到目前为止,我喜欢介绍parallel "关键字"(伪装后的对象构造函数)并在结果上调用方法的API。 代码更详细的版本将作为回复发布到该线程,并可能发布到CPAN。


2
如果我没记错的话,for/foreach 都会先获取整个列表,所以懒惰地评估列表将被完全读取,然后才开始遍历元素。因此,我认为除了使用 while 循环之外别无选择。但我可能错了。
while 循环的优点是可以通过代码引用来模拟懒惰评估列表的感觉。
my $list = sub { return calculate_next_element };
while(defined(my $element = &$list)) {
    ...
}

毕竟,在Perl 5中,我想领带可能是你能得到的最接近的东西了。


为什么不直接使用 $list = &calculate_next_element;?或者跳过代码引用,直接调用 calculate_next_element 函数呢? - cjm
在使用范围运算符时,对于for/foreach循环不会获取整个列表。否则,它们会获取整个列表。 - user11318
cjm:那只是一个占位符,用于在此计算下一个元素,不是真正的函数调用。当然,你是正确的。 - jkramer
对于数组而言,for循环无法获取整个列表。 - ysth

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接