惰性列表包装在 IO 中
Lazy list wrapped in IO
假设代码
f :: IO [Int]
f = f >>= return . (0 :)
g :: IO [Int]
g = f >>= return . take 3
当我在ghci中运行 g
时,导致Whosebug。但我在想也许它可以被懒惰地评估并产生 [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
)。尝试是懒惰地构建列表,即使我们在 IO
monad 中也是如此。这样阅读 h
结果的人甚至可以在列表完成之前就开始使用列表(因为它甚至可能是无限的)。如果阅读结果的人无论如何只关心前 3
个条目,则甚至不必构建列表的其余部分。我觉得这是不可能的:/.
是的,IO
是罪魁祸首。 >>=
对于 IO
在 "state of the world" 中是严格的。如果你写 m >>= h
,你将得到一个首先执行操作 m
的操作,然后将 h
应用于结果,最后执行操作 h
yields。您的 f
操作不 "do anything" 并不重要;无论如何都必须执行。因此,您最终陷入无限循环,一遍又一遍地开始 f
操作。
谢天谢地,有办法解决这个问题,因为 IO
是 MonadFix
的一个实例。您可以 "magically" 从该操作中访问 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 中甚至有一点语法糖,让您可以使用带有 rec
关键字的 do
符号或 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。
我不确定这是否是一种合适的用法,但是 unsafeInterleaveIO
会通过将 f
的 IO 操作推迟到f
要求:
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]
听起来您想要一个混合了列表和 IO
功能的 monad。幸运的是,这正是 ListT
的用途。这是您在该表单中的示例,其中 h'
计算 Collatz 序列并询问用户他们对序列中每个元素的感受(我真的想不出任何符合您轮廓形状的令人信服的东西) .
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
相比的权衡。
假设代码
f :: IO [Int]
f = f >>= return . (0 :)
g :: IO [Int]
g = f >>= return . take 3
当我在ghci中运行 g
时,导致Whosebug。但我在想也许它可以被懒惰地评估并产生 [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
)。尝试是懒惰地构建列表,即使我们在 IO
monad 中也是如此。这样阅读 h
结果的人甚至可以在列表完成之前就开始使用列表(因为它甚至可能是无限的)。如果阅读结果的人无论如何只关心前 3
个条目,则甚至不必构建列表的其余部分。我觉得这是不可能的:/.
是的,IO
是罪魁祸首。 >>=
对于 IO
在 "state of the world" 中是严格的。如果你写 m >>= h
,你将得到一个首先执行操作 m
的操作,然后将 h
应用于结果,最后执行操作 h
yields。您的 f
操作不 "do anything" 并不重要;无论如何都必须执行。因此,您最终陷入无限循环,一遍又一遍地开始 f
操作。
谢天谢地,有办法解决这个问题,因为 IO
是 MonadFix
的一个实例。您可以 "magically" 从该操作中访问 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 中甚至有一点语法糖,让您可以使用带有 rec
关键字的 do
符号或 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。
我不确定这是否是一种合适的用法,但是 unsafeInterleaveIO
会通过将 f
的 IO 操作推迟到f
要求:
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]
听起来您想要一个混合了列表和 IO
功能的 monad。幸运的是,这正是 ListT
的用途。这是您在该表单中的示例,其中 h'
计算 Collatz 序列并询问用户他们对序列中每个元素的感受(我真的想不出任何符合您轮廓形状的令人信服的东西) .
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
相比的权衡。