Haskell 处理 [IO String]

4
我有以下函数:

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

我希望我可以在这个列表上使用所有强大的列表函数,如filter等。但是我的haskell中关于IO monad的知识还有待提高。

在使用C#的Rx之后,我被io_stuff列表的概念所说服。

在haskell中有没有办法做到我想要的事情?类似于:

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

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

谢谢!

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

(sequencemapM实际上是由预置模块导出并默认可用的。)

例如,让我们看一下你的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

啊,这正是我正在寻找的!谢谢!! - Kr0e
2
此外,请查看replicateMreplicateM_ - hammar

8

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

更接近C# Rx的解决方案是使用类似于 pipes 的流库。

例如,您可以定义一个 Producer,从用户输入生成 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>

不错,肯定更接近 Rx。谢谢! - Kr0e

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接