热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

为何在某些情况下能从右侧折叠无限列表?

探讨了在处理无限列表时,从右侧折叠操作的成功与失败原因,特别是针对不同数据类型的实现差异。

在我完成CIS 194课程的家庭作业6的第5部分时,遇到了一个有趣的问题。该课程要求我们在不使用可分性测试的情况下实现一个标尺函数。我发现在构建这个函数时,可以通过连续将无限列表中的值插入累加器来实现这一目标。

例如,通过以下步骤构建标尺函数:

nats = [0,1,2,3,...]
[3]
[2,3,2]
[1,2,1,3,1,2,1]
[0,1,0,2,0,1,0,3,0,1,0,2,0]

接下来,我尝试将这一算法应用于Stream数据类型,这是一种没有nil终结符的列表形式。结果如预期,由于尝试从右侧折叠,程序出现了栈溢出错误。然而,同样的算法在普通的无限列表上却能正常工作。

data Stream a = Cons a (Stream a)
streamToList :: Stream a -> [a]
streamToList (Cons x xs) = x : streamToList xs
instance Show a => Show (Stream a) where
show = show . take 20 . streamToList
streamFromSeed :: (a -> a) -> a -> Stream a
streamFromSeed f x = Cons x (streamFromSeed f (f x))
nats :: Stream Integer
nats = streamFromSeed succ 0
interleave x (Cons y ys) = Cons x (Cons y (interleave x ys))
foldStream f (Cons x xs) = f x (foldStream f xs)
ruler = foldStream interleave nats

这让我感到困惑,为什么一种实现有效而另一种则导致错误?

解答一

问题在于你的interleave函数不够懒惰。在无限结构上进行右折叠的关键是,在执行首次计算之前不应过度检查折叠值的结果。因此,可以修改interleave函数如下:

interleave x stream = Cons x $ case stream of
Cons y ys -> Cons y (interleave x ys)

这样可以在检查stream之前就产生Cons x _;而你原来的版本在传递给等式右侧之前需要对stream进行一些评估,实际上迫使整个折叠在生成任何构造器之前完成。

解答二

我们可以查看foldr的源码来理解这个问题。简化后的版本如下:

foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)

Haskell是一种非严格(懒惰)求值的语言,这意味着除非必要,否则不会评估表达式。因此,除非确实需要,(foldr f z xs)不会被评估,这意味着如果f不依赖于其第二个参数(例如,当第一个项x具有特定值时),它就不会评估累加器。

例如,考虑一个takeWhileNeq函数的实现:

takeWhileNeq a = foldr f []
where f x xs -> if x == a then [] else (x:xs)

当我们用takeWhileNeq 2 [1,4,2,5]调用此函数时,它不会评估任何东西。但如果我们要打印结果,则会逐步评估:

   f 1 (foldr f [4,2,5])
- (1 == 2) -> False -> 1 : foldr f [4,2,5]
- (4 == 2) -> False -> 1 : (4 : foldr f [2,5])
- (2 == 2) -> True -> 1 : (4 : [])

对于无限列表,一旦遇到终止条件,它就会立即停止评估,从而避免了无限循环或栈溢出的问题。


推荐阅读
  • 本题探讨如何通过最大流算法解决农场排水系统的设计问题。题目要求计算从水源点到汇合点的最大水流速率,使用经典的EK(Edmonds-Karp)和Dinic算法进行求解。 ... [详细]
  • 本文深入探讨了 Java 中的 Serializable 接口,解释了其实现机制、用途及注意事项,帮助开发者更好地理解和使用序列化功能。 ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 本文介绍如何使用 Android 的 Canvas 和 View 组件创建一个简单的绘图板应用程序,支持触摸绘画和保存图片功能。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
  • 在金融和会计领域,准确无误地填写票据和结算凭证至关重要。这些文件不仅是支付结算和现金收付的重要依据,还直接关系到交易的安全性和准确性。本文介绍了一种使用C语言实现小写金额转换为大写金额的方法,确保数据的标准化和规范化。 ... [详细]
  • 本文介绍如何使用布局文件在Android应用中排列多行TextView和Button,使其占据屏幕的特定比例,并提供示例代码以帮助理解和实现。 ... [详细]
  • 本文详细介绍了 org.apache.commons.io.IOCase 类中的 checkCompareTo() 方法,通过多个代码示例展示其在不同场景下的使用方法。 ... [详细]
  • 使用lambda表达式排序Collections.sort(temp,(Stringa,Stringb)-{returnb.compareTo(a);});Collections ... [详细]
  • 历经三十年的开发,Mathematica 已成为技术计算领域的标杆,为全球的技术创新者、教育工作者、学生及其他用户提供了一个领先的计算平台。最新版本 Mathematica 12.3.1 增加了多项核心语言、数学计算、可视化和图形处理的新功能。 ... [详细]
  • 在尝试使用C# Windows Forms客户端通过SignalR连接到ASP.NET服务器时,遇到了内部服务器错误(500)。本文将详细探讨问题的原因及解决方案。 ... [详细]
author-avatar
拍友2502869293
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有