Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/haskell/10.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何处理Haskell中的无限IO对象列表?_Haskell_Io_Monads_Infinite Loop_Lazy Evaluation - Fatal编程技术网

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

如何处理Haskell中的无限IO对象列表?,haskell,io,monads,infinite-loop,lazy-evaluation,Haskell,Io,Monads,Infinite Loop,Lazy Evaluation,我正在写一个从文件列表中读取的程序。每个文件要么包含指向下一个文件的链接,要么将其标记为链的末端 作为Haskell的新手,处理这个问题的惯用方法似乎是为此目的列出一个可能的文件列表 getFirstFile :: String -> DataFile getNextFile :: Maybe DataFile -> Maybe DataFile loadFiles :: String -> [Maybe DataFile] loadFiles = iterate getNe

我正在写一个从文件列表中读取的程序。每个文件要么包含指向下一个文件的链接,要么将其标记为链的末端

作为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
getFirstFile::String->IO数据文件
getNextFile::可能是数据文件->IO(可能是数据文件)
loadFiles::String->[IO可能是数据文件]

loadFiles=iterate(getNextFile=正如您所注意到的,IO结果不能是惰性的,因此您不能(轻松地)使用IO构建无限列表。但是,在中有一个解决方法;有了它,您可以执行以下操作:

ioList startFile = do
    v <- processFile startFile
    continuation <- unsafeInterleaveIO (nextFile startFile >>= ioList)
    return (v:continuation)
ioList startFile=do
v=ioList)
返回(v:续)
不过,在这里要小心一点很重要——你只是把
ioList
的结果推迟到了将来某个不可预测的时间。事实上,它可能永远不会运行。所以当你聪明的时候要非常小心™ 像这样


就个人而言,我只需要构建一个手动递归函数。

这是朝着正确方向迈出的一步

sequenceWhile :: Monad m => (a -> Bool) -> [m a] -> m [a]
sequenceWhile _ [] = return []
sequenceWhile p (m:ms) = do
  x <- m
  if p x
    then liftM (x:) $ sequenceWhile p ms
    else return []
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 []
让我困惑的是
getNextFile
。和我一起走进一个简化的世界,在那里我们还没有处理IO。类型是
Maybe DataFile->Maybe DataFile
。在我看来,这应该是
数据文件->可能是数据文件
,我将在假设这种调整是可能的情况下进行操作。这看起来是
unfover
的一个很好的候选者。我要做的第一件事是制作我自己的简化版Unfover,它不太通用,但使用起来更简单

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->maybeaa
匹配
getNextFile::DataFile->maybea DataFile

getFiles :: String -> [DataFile]
getFiles = myUnfoldr getNextFile . getFirstFile
很漂亮,对吧
Unfover
非常类似于
iterate
,除非它点击了
Nothing
,否则会终止列表

现在,我们有一个问题<代码>IO
。我们怎样才能对抛出的
IO
执行相同的操作?甚至不要考虑不应命名的函数。我们需要一个增强的展开器来处理这个问题。幸运的是,我们可以使用

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 Unfolder
几乎为我们提供了正确的类型,但这一次不会完全减少

实际解决方案

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 []
现在我们都准备好了,就像以前一样

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
请记住,
myunfover
对于“下一个种子”和“当前输出值”相同的情况是一种简化的展开,collatz也是如此。这种行为应该很容易看到给定的
myunfover
的简单定义,即
unfover
tuplefy x=(x,x)


更多,大部分是无关的想法

剩下的和这个问题毫无关系,但我忍不住想了想。我们可以根据
myunfowrm
定义
myunfowrm

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->mb)->a->mc


由于这些函数中的
Monad m
可以与
sinkM
Identity
Monad约束相统一。然而,这一点实际上是有用的。

懒惰和I/O是一个棘手的组合。使用
unsafeInterleaveIO
是在IO monad中生成惰性列表的一种方法(这是标准
getContents
readFile
和friends使用的技术)。然而,尽管这样做很方便,但它使纯代码暴露于可能的I/O错误中,并使释放资源(如文件句柄)变得不确定。这就是为什么现在大多数“严肃”的Haskell应用程序(尤其是那些关注效率的应用程序)使用称为枚举器和迭代器的东西来流式I/O。实现这一概念的黑客库是

您可能可以在应用程序中使用惰性I/O,但我想我还是会给出一个例子,说明处理此类问题的另一种方法。您可以找到更多关于迭代器和的深入教程

例如,您的数据文件流可以实现为如下所示的枚举器:

import Data.Enumerator
import Control.Monad.IO.Class (liftIO)

iterFiles :: String -> Enumerator DataFile IO b
iterFiles s = first where
    first (Continue k) = do
        file <- liftIO $ getFirstFile s
        k (Chunks [file]) >>== next file
    first step = returnI step

    next prev (Continue k) = do
        file <- liftIO $ getNextFile (Just prev)
        case file of
            Nothing -> k EOF
            Just df -> k (Chunks [df]) >>== next df
    next _ step = returnI step
导入数据。枚举器
导入控制.Monad.IO.Class(liftIO)
iterFiles::字符串->枚举器数据文件IO b
iterFiles=第一个位置
第一(继续k)=做
文件>==下一个文件
第一步=返回i步
下一个上一个(继续k)=执行
文件k EOF
只需df->k(块[df])>>==下一个df
下一步=返回步骤

尝试在google上搜索
takeWhileM
,我发现了一个与代码几乎相同的相关问题:哈哈,说到这里,我最近在hackage中添加了monadlist软件包。它有一个带有
(a->ma)->[a]->m[a]
takeWhileM
(对于MonadPlus返回值来说更一般)。我必须在那里添加我的
sequenceWhile
函数和
sequenceWhileM
(a->m Bool)。是的,这个答案治愈了疾病,而不仅仅是治疗症状!需要注意的重要事项:
myunfolder
myunfolderm
不会保留列表中的第一个文件;它被简单地认为是产生列表其余部分的种子。如果需要的话,这可以很容易地解决。我找不到
unfolm
的组合词,但我成功地把它变成了完全不可理解的一行:
unform f=(>>=maybe(return[])(uncurry$\a->liftM(a:).unform f))。f
(标识)
(运行标识)
a->Identity b
之间转换
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
import Data.Enumerator
import Control.Monad.IO.Class (liftIO)

iterFiles :: String -> Enumerator DataFile IO b
iterFiles s = first where
    first (Continue k) = do
        file <- liftIO $ getFirstFile s
        k (Chunks [file]) >>== next file
    first step = returnI step

    next prev (Continue k) = do
        file <- liftIO $ getNextFile (Just prev)
        case file of
            Nothing -> k EOF
            Just df -> k (Chunks [df]) >>== next df
    next _ step = returnI step