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.
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