如何在 Haskell 中处理无限的 IO 对象列表?

2023-11-22

我正在编写一个从文件列表中读取的程序。每个文件要么包含到下一个文件的链接,要么标记它是链的末尾。

作为 Haskell 的新手,处理这个问题的惯用方法似乎是为此目的提供一个可能文件的惰性列表,我有

getFirstFile :: String -> DataFile
getNextFile :: Maybe DataFile -> Maybe DataFile

loadFiles :: String -> [Maybe DataFile]
loadFiles = iterate getNextFile . Just . getFirstFile

getFiles :: String -> [DataFile]
getFiles = map fromJust . takeWhile isJust . loadFiles

到目前为止,一切都很好。唯一的问题是,由于 getFirstFile 和 getNextFile 都需要打开文件,因此我需要将它们的结果放在 IO monad 中。这给出了修改后的形式

getFirstFile :: String -> IO DataFile
getNextFile :: Maybe DataFile -> IO (Maybe DataFile)

loadFiles :: String -> [IO Maybe DataFile]
loadFiles = iterate (getNextFile =<<) . Just . getFirstFile

getFiles :: String -> IO [DataFile]
getFiles = liftM (map fromJust . takeWhile isJust) . sequence . loadFiles

这样做的问题是,由于 iterate 返回一个无限列表,因此序列变成了无限循环。我不知道如何从这里继续。是否有一种更惰性的序列形式不会命中所有列表元素?我是否应该重新调整映射并 takeWhile 以便在每个列表元素的 IO monad 内进行操作?或者我是否需要放弃整个无限列表过程并编写一个递归函数来手动终止列表?


在正确方向迈出的一步

让我困惑的是getNextFile。和我一起进入一个简化的世界,我们还没有处理 IO。类型是Maybe DataFile -> Maybe DataFile。在我看来,这应该只是DataFile -> Maybe DataFile,并且我将在这种调整是可能的假设下进行操作。和that看起来是一个不错的候选人unfoldr。我要做的第一件事是制作我自己的 Expandr 简化版本,它不太通用,但使用起来更简单。

import Data.List

-- unfoldr :: (b -> Maybe (a,b)) -> b -> [a]
myUnfoldr :: (a -> Maybe a) -> a -> [a]
myUnfoldr f v = v : unfoldr (fmap tuplefy . f) v
  where tuplefy x = (x,x)

现在的类型f :: a -> Maybe a火柴getNextFile :: DataFile -> Maybe DataFile

getFiles :: String -> [DataFile]
getFiles = myUnfoldr getNextFile . getFirstFile

很漂亮,对吧?unfoldr很像iterate,除非一旦击中Nothing,它终止列表。

现在,我们遇到了问题。IO。我们怎样才能做同样的事情IO扔在那里?甚至不think关于不可命名的函数。我们需要一个增强的展开器来处理这个问题。幸运的是,展开器的来源可供我们使用。

unfoldr      :: (b -> Maybe (a, b)) -> b -> [a]
unfoldr f b  =
  case f b of
   Just (a,new_b) -> a : unfoldr f new_b
   Nothing        -> []

现在我们需要什么?健康剂量IO. liftM2 unfoldr almost为我们提供了正确的类型,但这次不会完全削减它。

一个实际的解决方案

unfoldrM :: Monad m => (b -> m (Maybe (a, b))) -> b -> m [a]
unfoldrM f b = do
  res <- f b
  case res of
    Just (a, b') -> do
      bs <- unfoldrM f b'
      return $ a : bs
    Nothing -> return []

这是一个相当简单的转变;我想知道是否有一些组合器可以完成同样的任务。

有趣的事实:我们现在可以定义unfoldr f b = runIdentity $ unfoldrM (return . f) b

让我们再次定义一个简化的myUnfoldrM,我们只需撒上liftM在那里:

myUnfoldrM :: Monad m => (a -> m (Maybe a)) -> a -> m [a]
myUnfoldrM f v = (v:) `liftM` unfoldrM (liftM (fmap tuplefy) . f) v
  where tuplefy x = (x,x)

现在我们都准备好了,就像以前一样。

getFirstFile :: String -> IO DataFile
getNextFile :: DataFile -> IO (Maybe DataFile)

getFiles :: String -> IO [DataFile]
getFiles str = do
  firstFile <- getFirstFile str
  myUnfoldrM getNextFile firstFile

-- alternatively, to make it look like before
getFiles' :: String -> IO [DataFile]
getFiles' = myUnfoldrM getNextFile <=< getFirstFile

顺便说一下,我用以下命令对所有这些进行了类型检查data DataFile = NoClueWhatGoesHere,以及类型签名getFirstFile and getNextFile,其定义设置为undefined.


[编辑] 已更改myUnfoldr and myUnfoldrM表现得更像iterate,包括结果列表中的初始值。

[编辑]关于展开的更多见解:

如果你很难把头转过来,科拉兹序列这可能是最简单的例子之一。

collatz :: Integral a => a -> Maybe a
collatz 1 = Nothing -- the sequence ends when you hit 1
collatz n | even n    = Just $ n `div` 2
          | otherwise = Just $ 3 * n + 1

collatzSequence :: Integral a => a -> [a]
collatzSequence = myUnfoldr collatz

记住,myUnfoldr是“下一个种子”和“当前输出值”相同的情况的简化展开,就像 collat​​z 的情况一样。这种行为应该很容易看出myUnfoldr的简单定义为unfoldr and tuplefy x = (x,x).

ghci> collatzSequence 9
[9,28,14,7,22,11,34,17,52,26,13,40,20,10,5,16,8,4,2,1]

更多,大多是不相关的想法

其余的与这个问题完全无关,但我就是忍不住沉思。我们可以定义myUnfoldr按照myUnfoldrM:

myUnfoldr f v = runIdentity $ myUnfoldrM (return . f) v

看起来熟悉?我们甚至可以抽象这个模式:

sinkM :: ((a -> Identity b) -> a -> Identity c) -> (a -> b) -> a -> c
sinkM hof f = runIdentity . hof (return . f)

unfoldr = sinkM unfoldrM
myUnfoldr = sinkM myUnfoldrM

sinkM应该致力于“下沉”(与“提升”相反)形式的任何功能

Monad m => (a -> m b) -> a -> m c.

自从Monad m在这些功能中可以与Identity单子约束sinkM。然而,我什么也没看到 that sinkM实际上会有用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Haskell 中处理无限的 IO 对象列表? 的相关文章

随机推荐

  • 如何将 CSS 下拉菜单居中 [关闭]

    Closed 这个问题需要调试细节 目前不接受答案 我需要一些帮助 我有一个 CSS 下拉菜单 但我希望标题居中 这样在所有屏幕尺寸上它都会位于中间 因为目前它卡在左侧 http jsfiddle net y4vDC 任何帮助将不胜感激 下
  • 如何将指向成员函数的指针传递给 C 函数? [复制]

    这个问题在这里已经有答案了 可能的重复 使用 C 类成员函数作为 C 回调函数 我正在使用 C 库 winpcap 编写一个面向对象的库 我需要传递网络数据包到达时调用的回调函数作为函数指针 我想将成员函数指针传递给 winpcap 以保持
  • React Router:查询参数匹配?

    根据已接受的答案这个问题 React Router 4 不再匹配查询参数 如果我从与我的其中一个匹配的 URL 访问
  • .Net 中 stackalloc 的缓冲区溢出保护

    来自 stackalloc 的 C 参考 使用 stackalloc 会自动启用公共语言运行时 CLR 中的缓冲区溢出检测功能 如果检测到缓冲区溢出 则会尽快终止进程 以最大程度地减少执行恶意代码的机会 具体来说 NET实现了什么样的保护机
  • 对(十六进制)颜色进行排序以匹配彩虹

    我有一个以十六进制表示的颜色列表 我需要对它们进行排序以匹配彩虹中颜色的顺序 我可以硬编码排序顺序 但我觉得有一种更干净的方法 下面是一个函数 给定十六进制 RGB 颜色规范 返回其 HSV 颜色 import colorsys def g
  • 如何计算某个日期范围内有多少晚?

    我需要根据入住和退房日期计算住宿天数 入住酒店 最好的方法是什么 即 如果我有 Checkin 12 11 2009 15 00 hs Checkout 14 11 2009 12 00 hs Doing Checkout Checkin
  • 如何使 ON DELETE CASCADE 在 sqlite 3.7.4 中工作?

    我检查了几次功能列表 似乎级联应该可以工作 当我执行这个 python 脚本时 usr bin env python3 import sqlite3 print sqlite3 sqlite version con sqlite3 conn
  • 是否可以检测 ACTION_SEND Intent 是否成功?

    我有一个简单的 Android 应用程序 其代码如下 来自安卓文档 Intent sendIntent new Intent sendIntent setAction Intent ACTION SEND sendIntent putExt
  • Dockerfile 的优点

    我们可以创建 Docker 映像并将它们全部推送到 Hub 而无需 Dockerfile Dockerfile 为什么有用 它有什么优点呢 Dockerfile 的创建是一个非常耗时的过程 并且只能由人来完成 我想知道基于基础镜像的提交镜像
  • msysgit 的麻烦

    所以我似乎在设置 msysgit 时遇到了一些实际问题 我可以使用 putty 连接到我的 SSH 目录 ssh 用户 主机 端口 我有正确的钥匙 我也可以使用 plink 通过 plink P PORT user host i path
  • jVectorMap 渲染太小

    我的 jVectorMap 没有采用我在包含的 div 上提供的新高度 并且仅以默认 高度 54px 进行渲染 这是我的 script js 文件中的 document ready 函数 team map usa vectorMap map
  • MS Project 甘特图控件在 C# 中的使用

    有人用过 C 中的 MS Project 甘特图控件吗 如果是 您能分享一些与此相关的资源吗 您还可以检查甘特图库对于 WPF 或 Windows 窗体 它们不需要在客户端计算机上安装 Microsoft Project 但为项目和相关甘特
  • 中继器内的复选框,如何在检查更改功能中获取命令名称值

    您好 我的 asp net listview 项目模板中有上面的 html 标记 td td
  • Go 中什么时候应该使用 new ?

    在原始语言结构中使用似乎毫无意义 因为您无法指定任何类型的值 func main y new float fmt Printf Len d len y gt Len 0 对于结构来说 它使bit更有道理 但是说起来有什么区别y new my
  • 如何使用react-router使用私有路由?

    我想使用身份验证来创建安全路由 我已经在 App jsx 文件中定义了路由 我使用 Route 来定义要渲染的组件 在 App jsx 中
  • 如何删除所有重复项,以便数据框中不留下任何重复项?

    有一个类似的问题对于 PHP 但我正在使用 R 并且无法将解决方案转化为我的问题 我有一个包含 10 行和 50 列的数据框 其中一些行完全相同 如果我在它上面使用 unique 我会得到一行 比方说 类型 但我真正想要的是只得到那些只出现
  • ELF 标头魔法 - 为什么将 0x7F 放入其中?

    我读过的关于 ELF header magic 的每一个资源都指出它包含 ASCII 编码的 ELF 然后简短地提到 0x7F 被添加到它前面而没有解释 0x7F有什么原因吗 是为了避免与现有格式发生冲突吗 是否符合现有标准 用于检测有关磁
  • Node.js 中类似“生成线程”的行为

    我想向一个小型 Web 应用程序添加一些管理实用程序 例如 备份数据库 用户单击按钮 HTTP 响应将立即返回 尽管可能长时间运行的进程已在后台启动 在 Java 中 这可能通过生成一个独立线程来实现 在 Scala 中则通过使用 Acto
  • 在WM6上查找存储卡路径

    有没有一种简单的方法可以在 Windows Mobile 设备上找到存储卡的路径 当有存储卡和蓝牙ftp连接时 挂载点通常是 Storage Card 但可以本地化为其他语言或由 OEM 修改 某些设备使用 SD Card 或其他挂载点 并
  • 如何在 Haskell 中处理无限的 IO 对象列表?

    我正在编写一个从文件列表中读取的程序 每个文件要么包含到下一个文件的链接 要么标记它是链的末尾 作为 Haskell 的新手 处理这个问题的惯用方法似乎是为此目的提供一个可能文件的惰性列表 我有 getFirstFile String gt