在Haskell中模仿带有IO的Python生成器

时间:2019-01-06 21:33:05

标签: haskell

这是一个关于如何在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]

我怀疑我会以错误的方式进行操作。

有什么建议吗?

1 个答案:

答案 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") 并不是交替使用FreeTm层只是fStream层的序列,无论创建顺序如何。只要m就可以了,因为相邻的f层总是可以Monad m在一起,并在相邻的m层之间形成新的join,直到恢复pure的交替结构。

如果您使用f(可能应该使用),则可能希望在同一软件包中将Of替换为FreeT

Stream

严格性对于(,)并不是特别有用,但是它确实读得更好。对于其他类型(例如data Of a b = !a :> b type Generator a = Stream (Of a) IO () )更有用。