Perl 6的这一方面是否有Perl解决方案用于延迟列出?
是否有人在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等效项以及一些其他功能。
每个函数都返回一个"生成器",该生成器是对绑定数组的引用。我们可以直接使用绑定数组,也可以使用许多访问器方法(例如迭代器)。