Haskel:如何强制执行功能评估并顺序写入文件?

时间:2019-04-19 08:50:04

标签: haskell file-io io html-parsing lazy-evaluation

我在Haskell中的惰性IO遇到了问题。尽管阅读了该领域的其他问题,但我仍然不知道如何解决我的具体情况。

我正在使用手术刀包来解析html。用例很简单:一个站点包含指向描述某种事件的其他站点的链接。因此,我编写了以下结构(这里省略了一些实现):

type Url = String

-- function that parses all urls
allUrls :: Url -> IO (Maybe [Url])

data Event = Event { ... }

-- function that parses an event
parseEvent :: Url -> IO (Maybe Event)

-- function that writes the event to a file
doThings :: Url -> IO ()
doThings url = return url >>= parseEvent >>= (appendFile "/tmp/foo.txt" . show)

-- function that should take all urls and write their events to a file
allEvents :: IO (Maybe [Url]) -> IO (Maybe (IO [()]))
allEvents urls = urls >>= return . liftM (mapM doThings)

-- or alternatively:

-- function that takes all urls and returns all events
allEvents :: IO (Maybe [Url]) -> IO (Maybe (IO [Maybe Event]))
allEvents urls = urls >>= return . liftM (mapM parseEvent)

-- some function that writes all events to a file
allEventsToFile :: IO (Maybe (IO [Maybe Event])) -> IO()
??? 

doThings函数按预期工作。给定一个url,它将解析相应的事件并将其写入文件。但由于懒惰,allEvents绝对不执行任何操作。如何在allEvents内部强制执行评估?

1 个答案:

答案 0 :(得分:4)

这不是懒惰IO的问题。惰性IO是您从文件中读取惰性字符串但不对其进行评估的情况–在这种情况下,运行时将延迟实际读取直到您对其进行评估。

问题实际上是您在allEvents中不做任何IO –您只是在IO functor 。这些值本身就是IO动作,但这没关系。特别地,根据单子法则a >>= return . f总是与fmap f a相同。 IO中的映射不会绑定动作。

在类型签名中已经观察到此问题:-> IO (Maybe (IO [()]))表示该函数产生IO操作,随后可以执行该操作。但是在这种情况下,您想在执行allEvents时执行所有操作。所以签名可能是

allEvents :: IO (Maybe [Url]) -> IO ()

(或-> IO (Either EventExecError ()),如果您想正确处理故障的话)。

这可能仍然不是您想要的:为什么您采用一个IO动作作为参数?这意味着allEvents本身将需要执行该操作才能首先获取URL,然后再进行自己的任何工作。那可能有它自己的副作用,并且对于不同的调用会给出不同的结果,您想要吗?

我想不是,所以应该是

allEvents :: Maybe [Url] -> IO ()

现在,您从一个简单的Maybe值开始,可以轻松地对其进行模式匹配:

allEvents Nothing = ?  -- perhaps simply `return ()`
allEvents (Just urls) = mapM_ doThings urls

然后在程序中使用它,您需要将url提取单绑定到事件执行:

main :: IO ()
main = do
  urlq <- allUrls
  allEvents urlq

...或简称allUrls >>= allEvents