【问题标题】:Is there a Perl solution for lazy lists this side of Perl 6?Perl 6 的这一侧是否有针对惰性列表的 Perl 解决方案?
【发布时间】:2008-09-21 00:26:55
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: perl lazylist source-filter


    【解决方案1】:

    如前所述,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 模块,它为惰性数组提供了更丰富、更完整的接口。我自己没有使用过模块,所以你的里程可能会有所不同。

    一切顺利,

    保罗

    【讨论】:

    • 如果您想了解有关此类编程的更多信息,请阅读 Mark Jason Dominus 的《Higher Order Perl》一书。很好,恕我直言。
    • for/foreach not 在范围运算符的特殊情况下获取整个列表。
    【解决方案2】:

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

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

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

    ……这样写:

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

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

    【讨论】:

      【解决方案3】:

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

      祝你好运,:)

      【讨论】:

        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

          让这个起死回生,提到我刚刚在CPAN 上写了模块List::Gen,这正是发帖者所寻找的:

          use List::Gen;
          
          for my $item ( @{gen { ... } \@list} ) {...}
          

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

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

          【讨论】:

          • 我去看看。谢谢。
          • 没问题,如果您认为其中应该包含任何功能,请告诉我。
          【解决方案6】:

          Use an iterator 或考虑使用来自 CPAN 的 Tie::LazyList(有点过时)。

          【讨论】:

            【解决方案7】:

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

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

            【讨论】:

              【解决方案8】:

              如果我没记错的话,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?
              • for/foreach 在范围运算符的情况下 not 获取整个列表。否则他们会这样做。
              • cjm:这只是作为 calculate-your-next-element-here 的占位符,而不是真正的函数调用。否则你当然是对的。
              • for 在数组的情况下也不会得到整个列表。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-06-04
              • 2012-11-14
              • 2019-12-16
              • 2011-01-19
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多