我在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内部强制执行评估?
答案 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
。