2013-09-02 2 views
10

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

В императивном языке программирования это будет выглядеть следующим образом:

content = '' 
while True: 
    line = readLine() 
    if line == 'q': 
     break 
    content += line 
func(content) 

Я нахожу это невероятно трудно сделать в Haskell, так что я хотел бы знать, если есть Haskell эквивалент.

ответ

7

В Хаскелле это достаточно просто. Самая сложная часть состоит в том, что вы хотите аккумулировать последовательность пользовательских входов. На императивном языке вы используете цикл для этого, тогда как в 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, которая возвращает String. Поскольку это действие ввода-вывода, вы получаете доступ к возвращенной строке, используя синтаксис <-, а не синтаксис назначения =. Если вам нужен быстрый обзор, я рекомендую прочитать The IO Monad For People Who Simply Don't Care.

Вы можете использовать эту функцию в строке GHCI как это

>>> str <- getUserLines 
Hello<Enter>  -- user input 
World<Enter>  -- user input 
q<Enter>   -- user input 
>>> putStrLn str 
Hello   -- program output 
World   -- program output 
+1

Теперь, вы действительно напишете это в Haskell? Добавление 'line' в' contents' каждый раз дает вам плохую производительность. «Содержимое», которое вы хотите в конце, является префиксом того, что вам даст один вызов 'getContents'. – nickie

+1

Это справедливый момент, но я подумал, что стоит объяснить, как это сделать «с нуля», чтобы почувствовать работу в монаде IO (что, вероятно, является самой запутанной частью Haskell для новичков). Он также имеет преимущество в том, чтобы отделить пользовательский ввод от обработки, которая будет выполняться во входе, что ваш ответ не делает. Я добавлю приложение о 'getContents.' –

+0

ОК, сделанный пункт, я возвращаю свой начальный -1. Но, образовательные цели остались в стороне, я бы рассмотрел код, как этот плохой Haskell. Для людей, которые заботятся, по крайней мере ... :-) – nickie

16

эквивалент 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 

Если вы читали первую строку кода справа налево, он говорит:

  1. прибудете все, что пользователь будет предоставлять в качестве входных данных (никогда не бойтесь, это лениво);

  2. разделить его на линии, когда он наступит;

  3. принимают только линии, если они не равны «q», останавливаются, когда вы видите такую ​​линию;

  4. и позвоните по номеру process для каждой строки.

Если вы еще не поняли это, вам необходимо внимательно изучить учебник Haskell!

+0

Я бы использовал getLine вместо readLn, чтобы получить исполняемую версию, а также добавить 'import Control.Monad' – jev

+0

@jev, согласился. Я использовал 'readLn', чтобы сделать его более общим, но может быть запутанным для тех, кто просто хочет читать строки. – nickie

1

Вы могли бы сделать что-то вроде

import Control.Applicative ((<$>)) 

input <- unlines . takeWhile (/= "q") . lines <$> getContents 

Затем вход будет то, что пользователь не написал до (но не включая) кв.

2

Использование 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) 

Это будет течь вход и никогда не загружать более чем одну строку в памяти.

Смежные вопросы