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.