2012-05-07 42 views
13

Tôi có đoạn mã sau đây, mà tôi vượt qua để withFile:hGetContents quá lười biếng

text <- hGetContents hand 
let code = parseCode text 
return code 

Đây tay là một tay cầm hồ sơ hợp lệ, mở ra với ReadModeparseCode được chức năng của riêng tôi mà đọc đầu vào và trả về một Có thể. Vì nó là, chức năng thất bại và trả về Không có gì. Nếu, thay vào đó tôi viết:

text <- hGetContents hand 
putStrLn text 
let code = parseCode text 
return code 

Tôi nhận được như tôi cần.

Nếu tôi làm openFilehClose bản thân mình, tôi có cùng một vấn đề. Tại sao chuyện này đang xảy ra? Làm thế nào tôi có thể giải quyết nó một cách sạch sẽ?

Cảm ơn

+0

Bạn có thể hiển thị mã nơi bạn sử dụng 'hClose' không? Có vẻ như bạn đang đóng cửa trước khi yêu cầu đầu vào. –

Trả lời

12

hGetContents không quá lười, nó chỉ cần được sáng tác với những thứ khác một cách thích hợp để có được hiệu quả mong muốn. Có thể tình hình sẽ rõ ràng hơn nếu nó được đổi tên thành exposeContentsToEvaluationAsNeededForTheRestOfTheAction hoặc chỉ listen.

withFile mở tệp, làm điều gì đó (hoặc không có gì, như bạn vui lòng - chính xác những gì bạn yêu cầu trong mọi trường hợp) và đóng tệp.

Nó sẽ hầu như không đủ để đưa ra tất cả những bí ẩn của 'lười biếng IO', nhưng xem xét tại sự khác biệt này trong bracketing

good file operation = withFile file ReadMode (hGetContents >=> operation >=> print) 
bad file operation = (withFile file ReadMode hGetContents) >>= operation >>= print 

-- *Main> good "lazyio.hs" (return . length) 
-- 503 
-- *Main> bad "lazyio.hs" (return . length) 
-- 0 

thô sơ đặt, bad sẽ mở ra và đóng lại các tập tin trước khi nó bất cứ điều gì; good thực hiện mọi thứ ở giữa việc mở và đóng tệp. Hành động đầu tiên của bạn là giống như bad.withFile nên chi phối tất cả các hành động bạn muốn thực hiện mà phụ thuộc vào xử lý.

Bạn không cần người thực thi nghiêm ngặt nếu bạn đang làm việc với String, tệp nhỏ, v.v., chỉ là ý tưởng về cách bố cục hoạt động. Một lần nữa, trong bad tất cả những gì tôi 'làm' trước khi đóng tệp là exposeContentsToEvaluationAsNeededForTheRestOfTheAction. Trong good tôi soạn exposeContentsToEvaluationAsNeededForTheRestOfTheAction với phần còn lại của hành động mà tôi có trong đầu, sau đó đóng tệp.

Các quen thuộc length + seq lừa được đề cập bởi Patrick, hoặc length + evaluate đáng để biết; hành động thứ hai của bạn với putStrLn txt là một biến thể. Nhưng tổ chức lại là tốt hơn, trừ khi lười biếng IO là sai cho trường hợp của bạn.

$ time ./bad 
bad: Prelude.last: empty list 
         -- no, lots of Chars there 
real 0m0.087s 

$ time ./good 
'\n'    -- right 
() 
real 0m15.977s 

$ time ./seqing 
Killed    -- hopeless, attempting to represent the file contents 
    real 1m54.065s -- in memory as a linked list, before finding out the last char 

Nó đi mà không nói rằng ByteString và văn bản có giá trị biết về, nhưng tổ chức lại với đánh giá trong tâm trí là tốt hơn, vì ngay cả với họ các biến thể Lazy thường những gì bạn cần, và sau đó họ đòi hỏi nắm bắt sự phân biệt tương tự giữa các hình thức thành phần. Nếu bạn đang đối phó với một trong những trường hợp (bao la) các trường hợp loại IO này không phù hợp, hãy xem enumerator, conduit và đồng sự, tất cả đều tuyệt vời.

+0

Sử dụng' eval' trên đọc 'String' là vô nghĩa, vì' evaluation' chỉ đánh giá WHNF, tức là hàm tạo '(:)' đầu tiên . Tuy nhiên, có thể thích hợp để sử dụng nó trên kết quả của ví dụ: phân tích cú pháp tệp, nếu điều đó phụ thuộc vào toàn bộ nội dung của tệp. – hammar

+0

Có, điều này được nêu trong tài liệu; Tôi đã đề cập nó vì nó được đề cập ở đâu đó ở đây. – applicative

+0

Những hacks 'length' này thực sự nổi loạn. – applicative

0

Bạn có thể buộc các nội dung của text được đánh giá sử dụng

length text `seq` return code 

như dòng cuối cùng.

9

hGetContents sử dụng IO lười; nó chỉ đọc từ tập tin khi bạn buộc nhiều hơn của chuỗi, và nó chỉ đóng xử lý tập tin khi bạn đánh giá toàn bộ chuỗi nó trả về. Vấn đề là bạn đang đính kèm nó trong withFile; thay vào đó, chỉ cần sử dụng trực tiếp openFilehGetContents (hoặc đơn giản hơn, readFile). Tệp sẽ vẫn bị đóng khi bạn đã đánh giá đầy đủ chuỗi. Một cái gì đó như thế này nên làm như lừa, để đảm bảo rằng các tập tin là hoàn toàn đọc và đóng cửa ngay lập tức bằng cách buộc toàn bộ chuỗi trước:

import Control.Exception (evaluate) 

readCode :: FilePath -> IO Code 
readCode fileName = do 
    text <- readFile fileName 
    evaluate (length text) 
    return (parseCode text) 

tình huống unintuitive như thế này là một trong những lý do người có xu hướng tránh lười biếng IO những ngày này , nhưng tiếc là bạn không thể thay đổi định nghĩa của hGetContents. Phiên bản IO nghiêm ngặt của hGetContents có sẵn trong gói strict, nhưng có thể không đáng giá tùy thuộc vào gói chỉ dành cho một chức năng đó.

Nếu bạn muốn tránh chi phí phát sinh từ việc vượt qua chuỗi hai lần tại đây, thì có thể bạn nên xem xét sử dụng loại hiệu quả hơn String, anyway; loại Textstrict IO equivalents cho nhiều chức năng IO String dựa trên, as does ByteString (nếu bạn đang xử lý dữ liệu nhị phân, thay vì văn bản Unicode).

+2

Tôi sẽ nói nó * là * có giá trị tùy thuộc vào 'nghiêm ngặt' chỉ dành cho' hGetContents' nghiêm ngặt; đó chính xác là những gì gói dành cho! Không lan truyền hội chứng NIH. –

+1

Định nghĩa 'hGetContents' trong' System.IO.Strict' là 'hGetContents h = IO.hGetContents h >> = \ s -> độ dài s \' seq \ 'trả về s'; đó là mẹo lâu đời nhất trong cuốn sách, không phải là một ý tưởng mới từ 'strict-0.3' – applicative

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