Perl 6 这边有针对惰性列表的 Perl 解决方案吗?

发布于 2024-07-05 23:19:49 字数 379 浏览 9 评论 0 原文

有人在 Perl 中找到了延迟评估列表的好解决方案吗? 我尝试了多种方法来将类似的东西变成

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

惰性求值——例如,通过绑定@list。 我试图避免分解并编写源过滤器来执行此操作,因为它们会扰乱您调试代码的能力。 有没有人成功过。 或者你只需​​要分解并使用 while 循环?

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

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

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

into a lazy evaluation--by tie-ing @list, for example. I'm trying to avoid breaking down and writing a source filter to do it, because they mess with your ability to debug the code. Has anybody had any success. Or do you just have to break down and use a while loop?

Note: I guess that I should mention that I'm kind of hooked on sometimes long grep-map chains for functionally transforming lists. So it's not so much the foreach loop or the while loop. It's that map expressions tend to pack more functionality into the same vertical space.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

橘虞初梦 2024-07-12 23:19:49

如前所述,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";
}

还有 Tie::Array::Lazy 模块,它为惰性数组提供了更丰富、更完整的接口。 我自己没有使用过该模块,因此您的体验可能会有所不同。

祝一切顺利,

保罗

As mentioned previously, for(each) is an eager loop, so it wants to evaluate the entire list before starting.

For simplicity, I would recommend using an iterator object or closure rather than trying to have a lazily evaluated array. While you can use a tie to have a lazily evaluated infinite list, you can run into troubles if you ever ask (directly or indirectly, as in the foreach above) for the entire list (or even the size of the entire list).

Without writing a full class or using any modules, you can make a simple iterator factory just by using closures:

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.
    };
}

And then to use it:

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

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

There's also the Tie::Array::Lazy module on the CPAN, which provides a much richer and fuller interface to lazy arrays. I've not used the module myself, so your mileage may vary.

All the best,

Paul

旧故 2024-07-12 23:19:49

[旁注:请注意,map/grep 链上的每个单独步骤都是急切的。 如果您一次给它一个大列表,您的问题会比最终的 foreach 更快出现。]

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

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

...这样写:

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

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

[Sidenote: Be aware that each individual step along a map/grep chain is eager. If you give it a big list all at once, your problems start much sooner than at the final foreach.]

What you can do to avoid a complete rewrite is to wrap your loop with an outer loop. Instead of writing this:

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

… write it like this:

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

This saves you from having to significantly rewrite your existing code, and as long as the list does not grow by several orders of magnitude during transformation, you get pretty nearly all the benefit that a rewrite to iterator style would offer.

南风起 2024-07-12 23:19:49

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

祝你好运, :)

If you want to make lazy lists, you'll have to write your own iterator. Once you have that, you can use something like Object::Iterate which has iterator-aware versions of map and grep. Take a look at the source for that module: it's pretty simple and you'll see how to write your own iterator-aware subroutines.

Good luck, :)

臻嫒无言 2024-07-12 23:19:49

至少有一种特殊情况,其中 for 和 foreach 已经过优化,不会立即生成整个列表。 这就是范围运算符。 所以你可以选择说:

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

这将迭代数组,根据你喜欢的方式进行转换,而无需预先创建一长串值。

如果您希望您的映射语句返回可变数量的元素,您可以这样做:

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

如果您希望比这更普遍的惰性,那么您最好的选择是学习如何使用闭包生成惰性列表。 MJD 的优秀著作 Higher Order Perl 可以引导您完成这些技术。 但请注意,它们将涉及对您的代码进行更大的更改。

There is at least one special case where for and foreach have been optimized to not generate the whole list at once. And that is the range operator. So you have the option of saying:

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

and this will iterate through the array, transformed however you like, without creating a long list of values up front.

If you wish your map statement to return variable numbers of elements, you could do this instead:

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

If you wish more pervasive laziness than this, then your best option is to learn how to use closures to generate lazy lists. MJD's excellent book Higher Order Perl can walk you through those techniques. However do be warned that they will involve far larger changes to your code.

贵在坚持 2024-07-12 23:19:49

起死回生,我刚刚在 List::Gen rel="nofollow noreferrer">CPAN 完全符合发帖者的要求:

use List::Gen;

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

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

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

Bringing this back from the dead to mention that I just wrote the module List::Gen on CPAN which does exactly what the poster was looking for:

use List::Gen;

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

all computation of the lists are lazy, and there are map / grep equivalents along with a few other functions.

each of the functions returns a 'generator' which is a reference to a tied array. you can use the tied array directly, or there are a bunch of accessor methods like iterators to use.

闻呓 2024-07-12 23:19:49

使用迭代器或考虑使用Tie::LazyList 来自 CPAN(有点过时了)。

Use an iterator or consider using Tie::LazyList from CPAN (which is a tad dated).

大海や 2024-07-12 23:19:49

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

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

I asked a similar question at perlmonks.org, and BrowserUk gave a really good framework in his answer. Basically, a convenient way to get lazy evaluation is to spawn threads for the computation, at least as long as you're sure you want the results, Just Not Now. If you want lazy evaluation not to reduce latency but to avoid calculations, my approach won't help because it relies on a push model, not a pull model. Possibly using Corooutines, you can turn this approach into a (single-threaded) pull model as well.

While pondering this problem, I also investigated tie-ing an array to the thread results to make the Perl program flow more like map, but so far, I like my API of introducing the parallel "keyword" (an object constructor in disguise) and then calling methods on the result. The more documented version of the code will be posted as a reply to that thread and possibly released onto CPAN as well.

夏九 2024-07-12 23:19:49

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

while 循环的优点是,您可以通过代码参考来伪造延迟评估列表的感觉:

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

毕竟,我想在 Perl 5 中,平局是您能得到的最接近的结果。

If I remember correctly, for/foreach do get the whole list first anyways, so a lazily evaluated list would be read completely and then it would start to iterate through the elements. Therefore, I think there's no other way than using a while loop. But I may be wrong.

The advantage of a while loop is that you can fake the sensation of a lazily evaluated list with a code reference:

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

After all, I guess a tie is as close as you can get in Perl 5.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文