Haskell 中的无限循环和 TUI 中断

Endless loop and a break for TUI in Haskell

我想监听按键并根据按键使用来自 System.Console.ANSI

的命令

为我的程序操作控制台界面的包。

在Python我会这样

while True:
    read_from_console()
    if condition:
        print_stuff_into_console
        break

如何以最简单的方式处理 Haskell 中的此类任务? 谢谢

Haskell 中等效的抽象伪代码如下所示:

loop = do
    line <- readFromConsole
    if condition line
        then do
            printStuffToConsole
            loop  -- Recurse - i.e. repeat the same thing again
        else 
            pure ()  -- Don't recurse - the function execution ends

 main = loop

当然,魔鬼会在 readFromConsoleprintStuffToConsole 的外观上。这些真的取决于你到底想做什么。

我将提供最愚蠢的实现,只是为了说明一切如何工作并构建一个完整的程序。

假设 "read from console" 只是让用户输入一行文本并按 Enter 键。为此,您可以使用 the getLine function:

readFromConsole = getLine

假设您想每次都打印相同的内容。对于打印,您可以使用 the putStrLn function:

printStuffToConsole = putStrLn "Give me another!"

然后假设停止条件是用户输入"STOP"。这可以用字符串比较来表示:

condition line = line /= "STOP"

如果将所有这些放在一起,就会得到一个完整的程序:

loop = do
    line <- readFromConsole
    if condition line
        then do
            printStuffToConsole
            loop  -- Recurse - i.e. repeat the same thing again
        else 
            pure ()  -- Don't recurse - the function execution ends

    where
        readFromConsole = getLine
        printStuffToConsole = putStrLn "Give me another!"
        condition line = line /= "STOP"

 main = loop

当然,虽然对程序的某些部分进行语义命名是件好事,但严格来说,如果您想让整个程序更短,则不必这样做:

main = do
    line <- getLine
    if line /= "STOP"
        then do
            putStrLn "Give me another!"
            main
        else 
            pure ()

Fyodor Soikin 已经提供了简单的方法。

在这里,我将评论 "break" 循环的一般方法:使用延续和 callCC

import Control.Monad.Cont

main :: IO ()
main = do
  putStrLn "start"
  flip runContT return $ callCC $ \break -> forever $ do
    l <- lift $ getLine
    if l == "quit"
      then break ()
      else lift $ putStrLn $ "not a quit command " ++ l
    lift $ putStrLn "next iteration"
  putStrLn "end"

Continuations 是出了名的难以掌握,但是上面的代码并不太复杂。粗略的直觉如下。

forever库函数用于无限重复一个动作,它是Haskell相当于while true

flip runContT return $ callCC $ \f -> .... 部分意味着 "define f to be a break-like function, which will exit the " 立即阻止“....。在代码中,我调用 break 来说明这一点。调用 break () 中断 forever(和 returns 外面的 ()——如果我们写 x <- flip runContT .... 将其绑定到 x,我们可以使用该值)。

不过有一个缺点。在 .... 部分,我们不再在 IO monad 中工作,而是在 ContT () IO monad 中工作。这就是让我们调用 break () 的原因。为了在那里使用常规 IO,我们需要 lift IO 操作。所以,我们不能使用 putStrLn "..",而是需要使用 lift $ putStrLn ".."

其余的应该或多或少地简单易懂。

这是 GHCi 中的一个小演示。

> main
start
1 (typed by the user)
not a quit command 1
next iteration
2 (typed by the user)
not a quit command 2
next iteration
3 (typed by the user)
not a quit command 3
next iteration
4 (typed by the user)
not a quit command 4
next iteration
quit (typed by the user)
end

只为 break 使用延续是个好主意吗?可能是。如果您不熟悉这种技术,可能不值得。简单的递归方法看起来简单得多。