Haskell - петля над пользовательским вводом
У меня есть программа в haskell, которая должна читать произвольные строки ввода от пользователя, и когда пользователь закончен, накопленный вход должен быть отправлен в функцию.
На императивном языке программирования это будет выглядеть так:
content = ''
while True:
line = readLine()
if line == 'q':
break
content += line
func(content)
Я считаю, что это невероятно сложно сделать в haskell, поэтому я хотел бы знать, есть ли эквивалент haskell.
Ответы
Ответ 1
Это достаточно просто в Haskell. Самая сложная часть состоит в том, что вы хотите аккумулировать последовательность пользовательских входов. На императивном языке вы используете цикл для этого, тогда как в Haskell канонический способ - использовать рекурсивную вспомогательную функцию. Он будет выглядеть примерно так:
getUserLines :: IO String -- optional type signature
getUserLines = go ""
where go contents = do
line <- getLine
if line == "q"
then return contents
else go (contents ++ line ++ "\n") -- add a newline
На самом деле это определение действия IO, которое возвращает a String
. Поскольку это действие ввода-вывода, вы получаете доступ к возвращенной строке, используя синтаксис <-
, а не синтаксис назначения =
. Если вы хотите получить краткий обзор, я рекомендую прочитать IO Monad для людей, которые просто не заботятся.
Вы можете использовать эту функцию в приглашении GHCI, например
>>> str <- getUserLines
Hello<Enter> -- user input
World<Enter> -- user input
q<Enter> -- user input
>>> putStrLn str
Hello -- program output
World -- program output
Ответ 2
эквивалент Haskell для итерации - это рекурсия. Вам также нужно будет работать в монаде IO
, если вам нужно прочитать строки ввода. Общая картина:
import Control.Monad
main = do
line <- getLine
unless (line == "q") $ do
-- process line
main
Если вы просто хотите скопировать все строки чтения в content
, вам не нужно это делать. Просто используйте getContents
, который будет извлекать (лениво) весь пользовательский ввод. Просто остановитесь, когда увидите 'q'
. В совершенно идиоматическом Haskell все чтение может быть сделано в одной строке кода:
main = mapM_ process . takeWhile (/= "q") . lines =<< getContents
where process line = do -- whatever you like, e.g.
putStrLn line
Если вы читаете первую строку кода справа налево, она говорит:
-
получить все, что пользователь будет предоставлять в качестве входных данных (никогда не бойтесь, это лениво);
-
разделить его по строкам,
-
берут строки только в том случае, если они не равны "q", останавливаются, когда вы видите такую строку;
-
и вызовите process
для каждой строки.
Если вы еще не поняли это, вам нужно внимательно прочитать учебник Haskell!
Ответ 3
Используя pipes-4.0
, который выйдет в эти выходные:
import Pipes
import qualified Pipes.Prelude as P
f :: [String] -> IO ()
f = ??
main = do
contents <- P.toListM (P.stdinLn >-> P.takeWhile (/= "q"))
f contents
Это загружает все строки в память. Однако вы также можете обрабатывать каждую строку по мере ее создания:
f :: String -> IO ()
main = runEffect $
for (P.stdinLn >-> P.takeWhile (/= "q")) $ \str -> do
lift (f str)
Это приведет к потоку ввода и никогда не загрузит более одной строки в память.
Ответ 4
Вы можете сделать что-то вроде
import Control.Applicative ((<$>))
input <- unlines . takeWhile (/= "q") . lines <$> getContents
Тогда ввод будет тем, что пользователь написал до (но не включая) q.