2013-09-02 96 views
10

Tôi có một chương trình trong haskell phải đọc dòng đầu vào tùy ý từ người dùng và khi người dùng hoàn thành, đầu vào được tích lũy phải được gửi đến một hàm.Haskell - vòng lặp qua đầu vào của người dùng

Trong một ngôn ngữ lập trình bắt buộc này sẽ trông như thế này:

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

Tôi tìm thấy điều này vô cùng khó khăn để làm trong Haskell vì vậy tôi muốn biết nếu có một Haskell tương đương.

Trả lời

7

Thật đơn giản trong Haskell. Phần khó nhất là bạn muốn tích lũy chuỗi các đầu vào của người dùng. Trong một ngôn ngữ bắt buộc bạn sử dụng một vòng lặp để làm điều này, trong khi trong Haskell cách kinh điển là sử dụng một hàm trợ giúp đệ quy. Nó sẽ trông giống như sau:

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 

Đây thực sự là định nghĩa của hành động IO trả về String. Vì đây là một hành động IO, bạn truy cập chuỗi trả về bằng cách sử dụng cú pháp <- thay vì cú pháp gán =. Nếu bạn muốn có tổng quan nhanh, tôi khuyên bạn nên đọc The IO Monad For People Who Simply Don't Care.

Bạn có thể sử dụng chức năng này tại dấu nhắc GHCI như thế này

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

Bây giờ, bạn có thực sự viết điều này trong Haskell không? Việc thêm 'dòng' vào' nội dung' mọi lúc sẽ cho bạn hiệu suất kém. 'Nội dung' mà bạn muốn cuối cùng là tiền tố của một lệnh gọi đến' getContents' sẽ cung cấp cho bạn. – nickie

+1

Đó là một điểm công bằng - nhưng tôi nghĩ rằng nó là giá trị giải thích làm thế nào để làm điều này "từ mặt đất lên", để có được một cảm giác làm việc trong đơn nguyên IO (mà có lẽ là một phần khó hiểu nhất của Haskell cho người mới). Nó cũng có lợi ích của việc tách đầu vào của người dùng khỏi quá trình xử lý được thực hiện trong đầu vào, mà câu trả lời của bạn thì không. Tôi sẽ thêm một phụ lục về 'getContents.' –

+0

OK, lấy điểm, tôi lấy lại -1 ban đầu của mình. Nhưng, mục đích giáo dục bị gạt sang một bên, tôi sẽ xem xét mã như thế này Haskell xấu. Đối với những người quan tâm, ít nhất ... :-) – nickie

16

Tương đương Haskell với phép lặp lại là đệ quy. Bạn cũng sẽ cần phải làm việc trong đơn IO, nếu bạn phải đọc dòng đầu vào. Ảnh chung là:

import Control.Monad 

main = do 
    line <- getLine 
    unless (line == "q") $ do 
    -- process line 
    main 

Nếu bạn chỉ muốn tích lũy tất cả các dòng đọc trong content, bạn không phải làm như vậy. Chỉ cần sử dụng getContents sẽ truy xuất (lazily) tất cả đầu vào của người dùng. Chỉ cần dừng lại khi bạn nhìn thấy 'q'. Trong khá thành ngữ Haskell, tất cả đọc có thể được thực hiện trong một dòng mã:

main = mapM_ process . takeWhile (/= "q") . lines =<< getContents 
    where process line = do -- whatever you like, e.g. 
          putStrLn line 

Nếu bạn đọc những dòng đầu tiên của mã từ phải sang trái, nó nói:

  1. get mọi thứ mà người dùng sẽ cung cấp làm đầu vào (không bao giờ sợ hãi, điều này là lười biếng);

  2. chia thành các dòng theo thứ tự;

  3. chỉ nhận các dòng miễn là chúng không bằng "q", dừng lại khi bạn thấy một dòng như vậy;

  4. và gọi process cho mỗi dòng.

Nếu bạn chưa tìm ra, bạn cần đọc kỹ hướng dẫn Haskell!

+0

tôi muốn sử dụng getLine thay vì readLn để nhận phiên bản thực thi và cũng thêm 'Import Control.Monad' – jev

+0

@jev, đã đồng ý. Tôi đã sử dụng 'readLn' để làm cho nó tổng quát hơn, nhưng nó có thể gây nhầm lẫn cho một người chỉ muốn đọc các dòng. – nickie

1

Bạn có thể làm một cái gì đó giống như

import Control.Applicative ((<$>)) 

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

Sau đó, đầu vào sẽ là những gì người dùng viết lên cho đến khi (nhưng không bao gồm) các q.

2

Sử dụng pipes-4.0, được sắp ra cuối tuần này:

import Pipes 
import qualified Pipes.Prelude as P 

f :: [String] -> IO() 
f = ?? 

main = do 
    contents <- P.toListM (P.stdinLn >-> P.takeWhile (/= "q")) 
    f contents 

Đó tải tất cả các dòng vào bộ nhớ. Tuy nhiên, bạn cũng có thể xử lý từng dòng khi nó đang được tạo, quá:

f :: String -> IO() 

main = runEffect $ 
    for (P.stdinLn >-> P.takeWhile (/= "q")) $ \str -> do 
     lift (f str) 

Điều đó sẽ truyền đầu vào và không bao giờ nạp nhiều hơn một dòng vào bộ nhớ.