这是一个关于如何在Haskell中做某事的问题,这在Python3中很容易做到。
我有一个使用生成器的Python3程序,如下所示:
def gen(filename):
for line in open(filename):
line = line.rstrip()
print(f"From {filename} about to yield the line {line}")
yield line
print(f"From {filename} recently yielded the line {line}")
if "." in line:
yield from gen(line)
for line in gen("a.txt"):
print(f"Main program has {line}")
如果我给它一个包含以下内容的输入文件a.txt:
First line of a
b.txt
Third line of a
和另一个包含以下内容的输入文件b.txt:
First line of b
Second line of b
然后,正如我期望的那样,输出为:
From a.txt about to yield the line First line of a
Main program has First line of a
From a.txt recently yielded the line First line of a
From a.txt about to yield the line b.txt
Main program has b.txt
From a.txt recently yielded the line b.txt
From b.txt about to yield the line First line of b
Main program has First line of b
From b.txt recently yielded the line First line of b
From b.txt about to yield the line Second line of b
Main program has Second line of b
From b.txt recently yielded the line Second line of b
From a.txt about to yield the line Third line of a
Main program has Third line of a
From a.txt recently yielded the line Third line of a
我非常希望能够在Haskell中做同样的事情。我需要保留Python版本的基本结构,将gen()保留为可以在不同地方调用的函数(或半协程,如果要调用它的话)。
在尝试用Haskell编写代码时,我陷入了类似以下类型的可怕困境:
IO [IO String]
我怀疑我会以错误的方式进行操作。
有什么建议吗?
答案 0 :(得分:9)
您要查找的数据类型为FreeT
:
data FreeF f a b = Pure a | Free (f b)
newtype FreeT f m a = FreeT { runFreeT :: m (FreeF f a (FreeT f m a)) }
FreeT f m a
表示“ m
后跟f
的交替层,但在任何时候,除了f
层以外,还可以有一个终止的{{1 }}-值”。相当于您的生成器的这种类型的特定形式是
a
type Generator a = FreeT ((,) a) IO ()
是Generator a
计算和IO
生产的交替层,除了a
计算之一可以通过产生{{1}终止生成}。
IO
因此您可以通过以下方式写()
:
instance (Functor f, Monad m) => Monad (FreeT f m)
instance MonadTrans (FreeT f)
lift :: (MonadTrans t, Monad m) => m a -> t m a
lift :: Monad m => m a -> FreeT f m a
liftF :: (Functor f, MonadFree f m) => f a -> m a
liftF :: (Functor f, Monad m) => f a -> FreeT f m a
然后可以将该结构拆回:
gen
-- singleton generator
yield :: a -> Generator a
yield x = liftF (x, ())
-- empty generator (unused/part of when)
end :: Generator a
end = return ()
gen :: FilePath -> Generator String
gen path = do handle <- lift $ openFile path
fileLines <- lift $ lines <$> hGetContents handle
forM_ fileLines $ \line -> do
lift $ putStrLn $ "From " ++ path ++ " about to yield the line " ++ line
yield line
lift $ putStrLn $ "From " ++ path ++ " recently yielded the line " ++ line
when ('.' `elem` line) $ gen line
lift $ hClose handle
有一个堂兄,名为Stream
。这几乎是同一回事,除了它使用一些技巧来挤出更多性能。 (代价是现在的平等性比以前更加模糊,但是无论如何我们通常并不关心控制结构的平等性。)具体来说,main = iterT (\(line, continue) -> putStrLn ("Main program has " ++ line) >> continue)
(gen "a.txt")
并不是交替使用FreeT
和m
层只是f
和Stream
层的序列,无论创建顺序如何。只要m
就可以了,因为相邻的f
层总是可以Monad m
在一起,并在相邻的m
层之间形成新的join
,直到恢复pure
的交替结构。
如果您使用f
(可能应该使用),则可能希望在同一软件包中将Of
替换为FreeT
:
Stream
严格性对于(,)
并不是特别有用,但是它确实读得更好。对于其他类型(例如data Of a b = !a :> b
type Generator a = Stream (Of a) IO ()
)更有用。