Haskell 用IO包装的惰性列表
假设代码Haskell 用IO包装的惰性列表,haskell,lazy-evaluation,io-monad,Haskell,Lazy Evaluation,Io Monad,假设代码 f :: IO [Int] f = f >>= return . (0 :) g :: IO [Int] g = f >>= return . take 3 在ghci中运行g时,会导致堆栈溢出。但我在想,也许可以懒散地对它进行评估,并生成[0,0,0]包装在IO中。我怀疑这里应该归咎于IO,但我真的不知道。显然,以下工作: f' :: [Int] f' = 0 : f' g' :: [Int] g' = take 3 f' 编辑:事实上,我对拥有如此
f :: IO [Int]
f = f >>= return . (0 :)
g :: IO [Int]
g = f >>= return . take 3
在ghci中运行g
时,会导致堆栈溢出。但我在想,也许可以懒散地对它进行评估,并生成[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::a->IO[b或c]
HA=do
(r,a')>=返回。(十)
y@(右)->返回[y]
h'::IO(b c,a中的任意一个)
--不平凡的事
main::IO()
main=mapM_uu打印。假设3=是的,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
有关使用MonadFix
的更有趣的示例,请参阅。我不确定这是否是一个合适的用法,但是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。幸运的是,这正是我们的目的。这是你的例子,用一个h'
计算Collatz序列,并询问用户对序列中每个元素的感觉(我真的想不出任何符合你轮廓形状的东西)
我想现代的方法可能会使用管道、导管、迭代器或其他东西,但我对它们的了解还不够,无法谈论与ListT
相比的折衷。我可能过于简单化了,我会编辑原始帖子。这似乎可以做到,谢谢。使用这个名称有什么缺点吗?名称不太令人放心;)。这就是为什么我说我不确定这是否合适。不愉快的交流有时是可以的,但总的来说是危险的;找一个比我更有经验的人来告诉你这是否是一个好主意。@Jakubdiel缺点是它会打乱你关于IO
如何发生的直觉:通常,在m>=f
中,我们期望m
的IO
发生在f
应用于参数之前;但是在unsafeInterleaveIO m>=f
中,m
的IO
将被延迟。将此转换为您的问题上下文,这意味着h
列表的使用者决定执行h'
的哪些迭代(如果有!)。编译器优化也很脆弱。因此,如果你在做一些你真正关心发生的IO
的事情——这几乎总是——这是一个非常糟糕的主意!h'
是否调用h
?你能告诉我们真正的代码吗?你想让它只执行足够的IO
来生成所需的结果,还是不管它是否都要执行?只是为了生成所需的结果,而实际的代码要大得多,更混乱,我仍然相信我在粘贴简化版本时会帮到它h
不是从h'
调用的,我犯了错误,我应该考虑一下。啊,好吧,如果你想让求值驱动执行,unsafeInterleaveIO
是你唯一的选择。这似乎是一个非常简单的方法,但是这个库有点讨厌——为什么它不导出ListT
构造函数呢?有没有可能有一天这个ListT
会取代transformers
中的一个呢?我能不能懒洋洋地把ListT IO(或者a b)
转换成IO[或者a b]
这样我就不必用列表的东西来说服打电话的人了?我本以为toList
是懒惰的,但它似乎不是:/我还想选择是使用take 3
还是仅仅使用identity,所以我认为流可能是无限的。@jakubdanial在main
的上述定义中,你当然可以用id
代替take 3
,没有问题。诚然,toList
在懒惰IO的意义上并不是懒惰的——基于我在另一个答案中概述的所有原因,您应该将其视为一件好事。在调用toList
之前,您需要决定是要调用take 3
还是id
。然而,toList
确实是将ListT IO(a b)
转换为IO[a b]
的正确方法。是的,但如果我选择id
则当函数未终止时,严格性会导致我无法读取中间结果:(这超出了使用ListT
而不是在我对原始问题的编辑中使用普通列表的目的。通常情况下,函数不会终止。
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]
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
> 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"