包含在IO中的惰性列表

jak*_*iel 3 haskell lazy-evaluation io-monad

假设代码

f :: IO [Int]
f = f >>= return . (0 :)

g :: IO [Int]
g = f >>= return . take 3
Run Code Online (Sandbox Code Playgroud)

当我g在ghci中运行时,它会导致stackoverflow.但我想也许它可以懒惰地评估并生产[0, 0, 0]包装IO.我怀疑这IO是责备,但我真的不知道.显然以下工作:

f' :: [Int]
f' = 0 : f'

g' :: [Int]
g' = take 3 f'
Run Code Online (Sandbox Code Playgroud)

编辑:事实上我对拥有这么简单的功能不感兴趣f,原始代码看起来更像是这样:

h :: a -> IO [Either b c]
h a = do
    (r, a') <- h' a
    case r of
        x@(Left  _) -> h a' >>= return . (x :)
        y@(Right _) -> return [y]

h' :: IO (Either b c, a)
-- something non trivial

main :: IO ()
main = mapM_ print . take 3 =<< h a
Run Code Online (Sandbox Code Playgroud)

h进行一些IO计算并Left在列表中存储invalid()响应,直到产生有效的response(Right).尝试是懒惰地构建列表,即使我们在IOmonad中.因此,读取结果的人h甚至可以在完成之前开始使用列表(因为它甚至可能是无限的).如果读取结果的人3无论如何都只关心第一个条目,那么甚至不必构建列表的其余部分.我感觉这是不可能的:/.

dfe*_*uer 7

是的,IO应该归咎于此.>>=因为IO在"世界状况"中是严格的.如果你写m >>= h,你会得到一个首先执行动作的动作m,然后应用于h结果,最后执行动作h产生.你的f行为不"做任何事" 并不重要; 它无论如何都必须执行.因此,你最终会在无限循环中f反复开始动作.

值得庆幸的是,有一种解决方法,因为IO是一个实例MonadFix.您可以"神奇地" IO从该操作中访问操作的结果.重要的是,访问必须足够懒惰,否则你会陷入无限循环.

import Control.Monad.Fix
import Data.Functor ((<$>))

f :: IO [Int]
f = mfix (\xs -> return (0 : xs))

-- This `g` is just like yours, but prettier IMO
g :: IO [Int]
g = take 3 <$> f
Run Code Online (Sandbox Code Playgroud)

甚至还有这样让你使用一点在GHC语法糖do符号与rec关键字或mdo符号.

{-# LANGUAGE RecursiveDo #-}

f' :: IO [Int]
f' = do
  rec res <- (0:) <$> (return res :: IO [Int])
  return res

f'' :: IO [Int]
f'' = mdo
  res <- f'
  return (0 : res)
Run Code Online (Sandbox Code Playgroud)

有关使用方法的更有趣示例MonadFix,请参阅Haskell Wiki.