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
当然,魔鬼会在 readFromConsole
和 printStuffToConsole
的外观上。这些真的取决于你到底想做什么。
我将提供最愚蠢的实现,只是为了说明一切如何工作并构建一个完整的程序。
假设 "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
使用延续是个好主意吗?可能是。如果您不熟悉这种技术,可能不值得。简单的递归方法看起来简单得多。
我想监听按键并根据按键使用来自 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
当然,魔鬼会在 readFromConsole
和 printStuffToConsole
的外观上。这些真的取决于你到底想做什么。
我将提供最愚蠢的实现,只是为了说明一切如何工作并构建一个完整的程序。
假设 "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
使用延续是个好主意吗?可能是。如果您不熟悉这种技术,可能不值得。简单的递归方法看起来简单得多。