【问题标题】:Haskell processing [IO String]Haskell 处理 [IO 字符串]
【发布时间】:2013-05-01 05:07:25
【问题描述】:

我有以下功能:

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

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

在将 Rx 用于 C# 之后,list-of-io_stuff-concept 说服了我。

有什么方法可以在 haskell 中做我想做的事吗? 比如:

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

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

谢谢!

【问题讨论】:

    标签: haskell io functional-programming monads


    【解决方案1】:

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

    更接近 C# 的 Rx 的解决方案是使用像 pipes 这样的流媒体库。

    例如,您可以定义一个Producer,它会根据用户输入生成Strings:

    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。谢谢!
    【解决方案2】:

    Control.Monad 中有一大堆普通的列表函数被修改为与 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其实是prelude导出的,默认可用。)

    例如,我们来看看你的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] -&gt; m ()。当您出于两个原因实际上并不关心结果时,这是一个不错的选择:它更明确地说明您的意图并且性能可以更好。

    所以如果你想打印 10 行而不是得到它们,你可以这样写:

    printLines = putStrLn "foo" : printLines
    
    main = sequence_ $ take 10 printLines
    

    【讨论】:

    猜你喜欢
    • 2011-10-01
    • 2021-10-18
    • 2011-09-17
    • 1970-01-01
    • 2011-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多