4

我有以下功能:

lines' :: [IO String]
lines' = getLine : lines'

我希望我可以使用这个列表中所有强大的列表函数,比如过滤器等。但是我对 haskell 中的 IO monad 的了解是可以改进的。

list-of-io_stuff-concept 在使用 Rx for C# 后说服了我。

有什么办法可以在 haskell 中做我想做的事吗?就像是:

ten_lines :: [IO String]
ten_lines = take 10 lines'

proc_lines :: [IO String]
proc_lines = [ (l, length l) | l <- lines' ]

谢谢!

4

2 回答 2

10

有一大堆普通的列表函数被修改为在Control.Monad. 您的问题特别感兴趣:

sequence :: Monad m => [m a] -> m [a]
mapM     :: Monad m => (a -> m b) -> [a] -> m [b]
filterM  :: Monad m => (a -> m Bool) -> [a] -> m [a]
foldM    :: Monad m => (a -> b -> m a) -> a -> [b] -> m a

sequence并且mapM实际上是由前奏导出的,默认情况下可用。)

例如,让我们看一下您的take 10 lines'示例的类型:

Prelude Control.Monad> :t take 10 lines'
take 10 lines' :: [IO String]

我们想把它[IO String]变成一个单一的IO [String]动作。这正是这样sequence做的!我们可以通过类型签名来判断这一点。所以:

sequence $ take 10 lines'

会做你想做的。

这些函数中的大多数还有一个以 结尾的版本_,例如sequence_. 这与普通函数的效果完全相同,只是它丢弃了结果,()而是返回。即,sequence_ :: [m a] -> m ()。当您出于两个原因实际上并不关心结果时,这是一个不错的选择:它更明确地说明您的意图并且性能可以更好。

因此,如果您想打印10 行而不是获取它们,您可以编写如下内容:

printLines = putStrLn "foo" : printLines

main = sequence_ $ take 10 printLines
于 2013-05-07T09:42:08.107 回答
8

Tikhon 的解决方案是最简单的解决方案,但它有一个主要缺陷:在处理整个列表之前它不会产生任何结果,如果处理太大的列表会溢出。

更接近 C# 的 Rx 的解决方案是使用流式库,例如pipes.

例如,您可以定义从用户输入Producer生成s 的 a:String

import Control.Monad
import Control.Proxy

lines' :: (Proxy p) => () -> Producer p String IO r
lines' () = runIdentityP $ forever $ do
    str <- lift getLine
    respond str

然后你可以定义一个需要 10 行的阶段:

take' :: (Monad m, Proxy p) => Int -> () -> Pipe p a a m ()
take' n () = runIdentityP $ replicateM_ n $ do
    a <- request ()
    respond a

...然后是处理阶段:

proc :: (Monad m, Proxy p) => () -> Pipe p String (String, Int) m r
proc () = runIdentityP $ forever $ do
    str <- request ()
    respond (str, length str)

...和最终输出阶段:

print' :: (Proxy p, Show a) => () -> Consumer p a IO r
print' () = runIdentityP $ forever $ do
    a <- request ()
    lift $ print a

现在您可以将它们组合成一个处理链并运行它:

main = runProxy $ lines' >-> take' 10 >-> proc >-> print'

...它会在输入每一行后立即输出处理后的结果,而不是在最后以批处理的形式提供结果:

$ ./pipes
Apple<Enter>
("Apple",5)
Test<Enter>
("Test",4)
123<Enter>
("123",3)
4<Enter>
("4",1)
5<Enter>
("5",1)
6<Enter>
("6",1)
7<Enter>
("7",1)
8<Enter>
("8",1)
9<Enter>
("9",1)
10<Enter>
("10",2)
$

实际上,您不必自己定义这些管道。pipes您可以从标准库中的组件组装相同的链:

>>> runProxy $ stdinS >-> takeB_ 10 >-> mapD (\x -> (x, length x)) >-> printD
<exact same behavior>
于 2013-05-07T14:06:59.390 回答