3

假设代码

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

g :: IO [Int]
g = f >>= return . take 3

当我g在 ghci 中运行时,它会导致 stackoverflow。但我在想也许它可以被懒惰地评估并[0, 0, 0]IO. 我怀疑IO是这里的罪魁祸首,但我真的不知道。显然,以下工作:

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

g' :: [Int]
g' = take 3 f'

编辑:事实上我对拥有这么简单的功能不感兴趣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

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

4

3 回答 3

7

是的,IO这是罪魁祸首。>>=forIO在“世界状态”中是严格的。如果你写m >>= h,你会得到一个动作,它首先执行动作m,然后应用h到结果,最后执行动作hyield。你的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

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)

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

于 2015-09-09T22:13:18.607 回答
3

听起来你想要一个混合了列表和IO. 幸运的是,这正是ListT它的用途。这是你的例子,h'它计算 Collat​​z 序列并询问用户他们对序列中每个元素的感觉(我真的想不出任何符合你轮廓形状的令人信服的东西)。

import Control.Monad.IO.Class
import qualified ListT as L

h :: Int -> L.ListT IO (Either String ())
h a = do
  (r, a') <- liftIO (h' a)
  case r of
    x@(Left  _) -> L.cons x (h a')
    y@(Right _) -> return y

h' :: Int -> IO (Either String (), Int)
h' 1 = return (Right (), 1)
h' n = do
  putStrLn $ "Say something about " ++ show n
  s <- getLine
  return (Left s, if even n then n `div` 2 else 3*n + 1)

main = readLn >>= L.traverse_ print . L.take 3 . h

这是它在 ghci 中的样子:

> main
2
Say something about 2
small
Left "small"
Right ()
> main
3
Say something about 3
prime
Left "prime"
Say something about 10
not prime
Left "not prime"
Say something about 5
fiver
Left "fiver"

我想现代方法会使用管道或导管或迭代器或其他东西,但我对它们了解得不够多,无法谈论与ListT.

于 2015-09-10T00:39:40.250 回答
2

我不确定这是否是一个合适的用法,但可以通过推迟 IO 操作直到请求内部的值unsafeInterleaveIO来获得您所要求的行为:ff

module Tmp where
import System.IO.Unsafe (unsafeInterleaveIO)

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

g :: IO [Int]
g = f >>= return . take 3

*Tmp> g
[0,0,0]
于 2015-09-09T23:01:05.440 回答