2013-06-23 106 views
6

current version of the Pipes tutorial的,使用以下两个功能中的示例中的一个:我如何摆脱Haskell中的循环?

stdout ::() -> Consumer String IO r 
stdout() = forever $ do 
    str <- request() 
    lift $ putStrLn str 

stdin ::() -> Producer String IO() 
stdin() = loop 
    where 
    loop = do 
     eof <- lift $ IO.hIsEOF IO.stdin 
     unless eof $ do 
      str <- lift getLine 
      respond str 
      loop 

如教程本身mentinoed,P.stdin是有点复杂,因为需要以检查输入的端。

是否有任何好的方法来重写P.stdin不需要手动尾递归循环,并使用像P.stdout这样的高阶控制流组合器呢?在命令式语言,我会用一个结构化的while循环或break语句做同样的事情:

while(not IO.isEOF(IO.stdin)){ 
    str <- getLine() 
    respond(str) 
} 

forever(){ 
    if(IO.isEOF(IO.stdin)){ break } 
    str <- getLine() 
    respond(str) 
} 

回答

9

看起来像一个工作,whileM_

stdin() = whileM_ (lift . fmap not $ IO.hIsEOF IO.stdin) (lift getLine >>= respond) 

,或者用做表示法类似最初的例子:

stdin() = 
    whileM_ (lift . fmap not $ IO.hIsEOF IO.stdin) $ do 
     str <- lift getLine 
     respond str 

monad-loops该包还提供whileM返回的中间结果的列表,而不是igno的响应重复动作的结果,以及其他有用的组合器。

+0

'whileM_'似乎也做了同样的事情,只是参数的“常规”顺序。那就是我正在寻找的东西。 – hugomg

+1

从某种意义上说,'whileM_'的参数顺序更自然。然而,这需要测试的倒置,我认为“做一些事情,直到EOF”比“当(而不是EOF)做某事时更自然”。个人喜好。 –

+0

我也只是注意到,直到M_检查循环体后的条件。这将有不同于orifinal代码的行为。 – hugomg

1

由于没有隐式流,所以没有像“break”这样的事情。此外,您的示例已经是小块​​,将用于更复杂的代码。

如果你想停止“产生字符串”,它应该被你的抽象支持。即一些在“Consumer”中使用特殊monad的“管道”的管理和/或与此相关的其他monad。

+0

不知道,我觉得这是一个比管道特定的问题更多的总体控制流问题。如果我们正在处理一元代码,那么肯定存在隐式控制流。 – hugomg

+0

@missingno,monads是用语言描述的显式流。大多数命令式语言都有隐式流。如果你不会用'do'符号,你会看到monads的精确组合。与描述一些“执行/解析/生成/计算流程等”的许多其他抽象一样。他们都描述了Haskell的内部。虽然语言本身并不适用于严格的执行顺序(懒惰只是可以继承的规则)。 – ony

10

我更喜欢以下内容:

import Control.Monad 
import Control.Monad.Trans.Either 

loop :: (Monad m) => EitherT e m a -> m e 
loop = liftM (either id id) . runEitherT . forever 

-- I'd prefer 'break', but that's in the Prelude 
quit :: (Monad m) => e -> EitherT e m r 
quit = left 

你使用这样的:

import Pipes 
import qualified System.IO as IO 

stdin ::() -> Producer String IO() 
stdin() = loop $ do 
    eof <- lift $ lift $ IO.hIsEOF IO.stdin 
    if eof 
    then quit() 
    else do 
     str <- lift $ lift getLine 
     lift $ respond str 

this blog post,我解释了这种技术。

我在教程中不使用它的唯一原因是我认为它不太适合初学者。

+0

看起来我们需要为所有以前的单点操作添加额外的“提升”级别。我们能够解决这个问题吗?还是这种基于变压器的解决方案的根本限制?我明白,不像while循环,'quit'应该在深度嵌套或其他功能时也可以工作,但我不愿意添加这些电梯是唯一的方法。 (我绝对同意,在实际的教程中没有必要使事情复杂化 - 只是它恰好是我所想的一个很好的例子) – hugomg

+1

@missingno我的经验法则是只有在'循环解决方案太麻烦了(即有很多情况下,循环,只有一个案件退出)。 –

0

你可以简单地进口System.Exit,并使用exitWith ExitSuccess

EG。 if(input =='q') then exitwithHost ExitSuccess else print 5(anything)