Бесконечный l oop и перерыв для TUI в Haskell - PullRequest
0 голосов
/ 17 января 2020

Я хочу прослушивать нажатия клавиш и в зависимости от них использовать команды из пакета System.Console.ANSI

для управления интерфейсом консоли для моей программы.

В Python Я бы хотел this

while True:
    read_from_console()
    if condition:
        print_stuff_into_console
        break

Как мне подойти к такой задаче в Haskell самым простым способом? Спасибо

Ответы [ 2 ]

3 голосов
/ 17 января 2020

Федор Сойкин уже предоставил простой способ сделать это.

Здесь я прокомментирую общий способ "сломать" все oop: использование продолжений и 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"

Продолжения печально сложны для gr asp, но приведенный выше код не слишком сложен. Грубая интуиция заключается в следующем.

Функция библиотеки forever используется для бесконечного повторения действия, это Haskell эквивалент while true.

Часть flip runContT return $ callCC $ \f -> .... означает "определить f для быть подобной прерыванию функцией, которая немедленно выйдет из «блока» * 1015. * В коде я вызываю это break, чтобы прояснить это. Вызов break () прерывает forever (и возвращает () снаружи - мы могли бы использовать это значение, если бы написали x <- flip runContT ...., чтобы связать его с x).

Однако есть и обратная сторона. В части .... мы больше не работаем внутри * Монада 1025 *, но в монаде ContT () IO. Это то, что позволяет нам называть 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? Может быть. Если вы не знакомы с этой техникой, вероятно, это не стоит т. Простой рекурсивный подход выглядит намного проще.

3 голосов
/ 17 января 2020

Эквивалентный абстрактный псевдо-i sh код в 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. И это действительно зависит от того, что именно вы хотели бы сделать.

Я предложу самую глупую из возможных реализаций, просто чтобы проиллюстрировать, как все работает, и построить полную программу.

Допустим, что «читать из консоли» означает, что пользователь вводит строку текста и нажимает Enter. Для этого вы можете использовать функцию getLine :

readFromConsole = getLine

И, скажем, вы хотите печатать одно и то же каждый раз. Для печати вы можете использовать функцию putStrLn :

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 ()
...