2016-12-03 16 views
5

On tôi journing hướng nắm lười biếng IO trong Haskell tôi thử như sau:reimplementing getContents sử dụng getchar

main = do 
    chars <- getContents 
    consume chars 

consume :: [Char] -> IO() 
consume [] = return() 
consume ('x':_) = consume [] 
consume (c : rest) = do 
    putChar c 
    consume rest 

mà chỉ Echos tất cả các ký tự gõ vào stdin cho đến khi tôi nhấn 'x'.

Vì vậy, tôi ngây thơ nghĩ rằng nó nên có thể để reimplement getContents sử dụng getChar làm một cái gì đó dọc theo dòng sau đây:

myGetContents :: IO [Char] 
myGetContents = do 
    c <- getChar 
    -- And now? 
    return (c: ???) 

Hóa ra nó không đơn giản như vậy kể từ khi ??? sẽ đòi hỏi một chức năng của loại IO [Char] -> [Char] mà sẽ - Tôi nghĩ - phá vỡ toàn bộ ý tưởng của đơn nguyên IO.

Kiểm tra việc triển khai getContents (hoặc thay vì hGetContents) cho biết toàn bộ nhà máy xúc xích của công cụ IO bẩn. Giả định của tôi có đúng là myGetContents không thể được triển khai mà không sử dụng dơ bẩn, tức là phá hoại, mã không?

Trả lời

6

Bạn cần một số nguyên thủy unsafeInterleaveIO :: IO a -> IO a mới trì hoãn việc thực hiện hành động đối số của nó cho đến khi kết quả của hành động đó sẽ được đánh giá. Sau đó,

myGetContents :: IO [Char] 
myGetContents = do 
    c <- getChar 
    rest <- unsafeInterleaveIO myGetContents 
    return (c : rest) 
+3

Hoạt động như một nét duyên dáng :) Còn lại để thêm rằng 'unsafeInterleaveIO' phải được nhập dưới dạng' System.IO.Unsafe'. – johanneslink

1

Bạn thực sự nên tránh sử dụng bất kỳ thứ gì trong System.IO.Unsafe nếu có thể. Họ có xu hướng giết tính minh bạch tham chiếu và không phải là các hàm phổ biến được sử dụng trong Haskell trừ khi thực sự cần thiết.

Nếu bạn thay đổi chữ ký loại của mình một chút, tôi nghi ngờ bạn có thể nhận được cách tiếp cận thành ngữ hơn cho vấn đề của mình.

consume :: Char -> Bool 
consume 'x' = False 
consume _ = True 

main :: IO() 
main = loop 
    where 
    loop = do 
     c <- getChar 
     if consume c 
     then do 
     putChar c 
     loop 
     else return() 
+0

Lời khuyên trong đoạn đầu tiên có ý nghĩa trong hầu hết các trường hợp, nhưng trong trường hợp này, nó không áp dụng vì OP rõ ràng và cố ý cố gắng triển khai lại I/O lười biếng. – duplode

+0

Để ghi lại câu trả lời của tôi dựa trên trao đổi trên twitter với OP không được phản ánh trong câu hỏi gốc. Tôi có ấn tượng rằng họ có thể không rõ ràng đã được sau khi "lười biếng IO" và chỉ muốn có được mã của họ để chạy. Tuy nhiên, với tinh thần của câu hỏi thực tế, đây thực sự là một câu trả lời sai. – bojo

0

Bạn có thể thực hiện việc này mà không cần bất kỳ hack nào.

Nếu mục tiêu của bạn chỉ đơn giản là đọc tất cả stdin thành String, bạn không cần bất kỳ chức năng nào trong số unsafe*.

IO là Monad và Monad là hàm Functor. Một functor được xác định bởi chức năng fmap, có chữ ký là:

fmap :: Functor f => (a -> b) -> f a -> f b 

thỏa mãn hai luật này:

fmap id = id 
fmap (f . g) = fmap f . fmap g 

hiệu quả, fmap áp dụng một chức năng để giá trị gói.

Với một ký tự cụ thể 'c', loại fmap ('c':) là gì? Chúng tôi có thể viết hai loại xuống, sau đó thống nhất họ:

fmap  :: Functor f => (a  -> b ) -> f a  -> f b 
    ('c':) ::    [Char] -> [Char] 
fmap ('c':) :: Functor f => ([Char] -> [Char]) -> f [Char] -> f [Char] 

Nhắc lại rằng IO là một functor, nếu chúng ta muốn xác định myGetContents :: IO [Char], có vẻ như hợp lý để sử dụng này:

myGetContents :: IO [Char] 
myGetContents = do 
    x <- getChar 
    fmap (x:) myGetContents 

này gần , nhưng không hoàn toàn tương đương với getContents, vì phiên bản này sẽ cố gắng đọc qua phần cuối của tệp và ném một lỗi thay vì trả về một chuỗi.Chỉ cần nhìn vào nó nên làm cho rõ ràng: không có cách nào để trả lại một danh sách cụ thể, chỉ là một chuỗi khuyết điểm vô hạn. Biết rằng trường hợp cụ thể là "" tại EOF (và sử dụng cú pháp ghi vào <$> cho fmap) đưa chúng ta đến:

import System.IO 
myGetContents :: IO [Char] 
myGetContents = do 
    reachedEOF <- isEOF 
    if reachedEOF 
    then return [] 
    else do 
    x <- getChar 
    (x:) <$> myGetContents 

Lớp applicative dành một (nhẹ) đơn giản hóa.

Nhớ lại rằng IO là một hàm Functor, không chỉ bất kỳ Functor cũ nào. Có "Luật applicative" gắn liền với typeclass này rất giống với "Luật functor", nhưng chúng tôi sẽ xem xét cụ thể tại <*>:

<*> :: Applicative f => f (a -> b) -> f a -> f b 

này là gần như giống hệt nhau để fmap (aka <$>), ngoại trừ chức năng áp dụng cũng được bao bọc. Sau đó chúng tôi có thể tránh được những ràng buộc tại khoản else của chúng tôi bằng cách sử dụng phong cách applicative:

import System.IO 
myGetContents :: IO String 
myGetContents = do 
    reachedEOF <- isEOF 
    if reachedEOF 
    then return [] 
    else (:) <$> getChar <*> myGetContents 

Một sửa đổi là cần thiết nếu đầu vào có thể là vô hạn.

Ghi khi tôi nói rằng bạn không cần unsafe* chức năng nếu bạn chỉ muốn đọc tất cả của stdin thành một String? Vâng, nếu bạn chỉ muốn một số số của thông tin đầu vào, bạn sẽ làm. Nếu đầu vào của bạn có thể dài vô hạn, bạn chắc chắn sẽ làm. Các chương trình chính thức khác ở chỗ một nhập khẩu và một từ duy nhất:

import System.IO 
import System.IO.Unsafe 
myGetContents :: IO [Char] 
myGetContents = do 
    reachedEOF <- isEOF 
    if reachedEOF 
    then return [] 
    else (:) <$> getChar <*> unsafeInterleaveIO myGetContents 

Chức năng xác định của lười biếng IO là unsafeInterleaveIO (từ System.IO.Unsafe). Điều này làm chậm sự tính toán của hành động IO cho đến khi được yêu cầu.

Các vấn đề liên quan