热门标签 | 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 : [])

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


推荐阅读
  • 本文探讨了STL迭代器的最佳实践,包括iterator与const_iterator、reverse_iterator及其const版本之间的关系,以及如何高效地转换和使用这些迭代器类型。 ... [详细]
  • Android中解析XML文件的实践指南
    本文详细介绍了在Android应用开发中解析XML文件的方法,包括从本地文件和网络资源获取XML文件的不同途径,以及使用DOM、SAX和PULL三种解析方式的具体实现。 ... [详细]
  • 724. 寻找数组的中轴索引
    给定一个整数数组 `nums`,编写一个方法返回该数组的“中轴”索引。定义中轴索引为该索引左侧所有数字之和等于右侧所有数字之和的索引。如果不存在这样的索引,则返回 -1。如果有多个中轴索引,返回最左边的一个。 ... [详细]
  • 本文探讨了如何使用pg-promise库在PostgreSQL中高效地批量插入多条记录,包括通过事务和单一查询两种方法。 ... [详细]
  • Java 架构:深入理解 JDK 动态代理机制
    代理模式是 Java 中常用的设计模式之一,其核心在于代理类与委托类共享相同的接口。代理类主要用于为委托类提供预处理、过滤、转发及后处理等功能,以增强或改变原有功能的行为。 ... [详细]
  • [Vue.js 3.0] Guide – Scaling Up – State Management
    [Vue.js 3.0] Guide – Scaling Up – State Management ... [详细]
  • KMP算法是一种高效的字符串模式匹配算法,能够在不进行回溯的情况下完成匹配,其时间复杂度为O(m+n),其中m和n分别为文本串和模式串的长度。本文将详细介绍KMP算法的工作原理,并提供C语言实现。 ... [详细]
  • 本文介绍了如何使用Java代码在Android设备上检测特定应用程序是否已安装。通过创建一个Intent并利用PackageManager查询该Intent的可用性来实现这一功能。 ... [详细]
  • Java实现文本到图片转换,支持自动换行、字体自定义及图像优化
    本文详细介绍了如何使用Java实现将文本转换为图片的功能,包括自动换行、自定义字体加载、抗锯齿优化以及图片压缩等技术细节。 ... [详细]
  • 本文将详细探讨 Linux 系统中的 netstat 命令,该命令用于查看网络状态和连接情况。通过了解 IP 地址和端口的基本概念,我们将更好地理解如何利用 netstat 命令来监控和管理网络服务。 ... [详细]
  • 本文介绍了一道来自《紫书》的编程题目——UVa11212 编辑书稿。该问题通过迭代加深搜索(IDA*)算法解决,旨在找到将给定排列转换为升序排列所需的最少步骤。文章提供了详细的解题思路和代码实现。 ... [详细]
  • Java Servlet中获取客户端IP与MAC地址的方法
    本文介绍了一种在Java Servlet应用中获取客户端IP地址及MAC地址的技术实现方法,通过示例代码详细解析了获取过程中的关键步骤和技术点。 ... [详细]
  • 本文探讨了在渗透测试中信息收集阶段使用的几种端口扫描技术,包括nmap、masscan、socket、telnet及nc等工具的应用与比较。 ... [详细]
  • 本文详细介绍了 Java 中 org.apache.commons.httpclient.HttpConnection 类的 getProxyPort 方法的使用方法和代码示例,帮助开发者更好地理解和应用此方法。 ... [详细]
  • 本文详细介绍了 Java 中 com.amazonaws.auth.SystemPropertiesCredentialsProvider 初始化方法的使用方式,并提供了多个实际的代码示例,帮助开发者更好地理解和应用这一方法。 ... [详细]
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社区 版权所有