2009-04-16 30 views
10

Xem xét chương trình Haskell sau đây. Tôi đang cố gắng để chương trình trong một "phong cách dòng", nơi chức năng hoạt động trên suối (thực hiện ở đây chỉ đơn giản là danh sách). Những thứ như normalStreamFunc hoạt động tốt với danh sách lười biếng. Tôi có thể chuyển một danh sách vô hạn tới normalStreamFunc và nhận ra một danh sách vô hạn khác, nhưng với một hàm được ánh xạ lên mỗi giá trị. Những thứ như effectfulStreamFunc không hoạt động tốt. Hành động IO có nghĩa là tôi cần đánh giá toàn bộ danh sách trước khi tôi có thể rút ra các giá trị riêng lẻ. Ví dụ, đầu ra của chương trình là:Luồng Haskell có hiệu ứng IO

a 
b 
c 
d 
"[\"a\",\"b\"]" 

nhưng những gì tôi muốn là một cách để viết effectfulStreamFunc để chương trình sản xuất này:

a 
b 
"[\"a\",\"b\"]" 

rời khỏi hành động còn lại unevaluated. Tôi có thể tưởng tượng một giải pháp bằng cách sử dụng unsafePerformIO, nhưng hãy nói rằng tôi lấy nó ra khỏi bảng. Đây là chương trình:

import IO 

normalStreamFunc :: [String] -> [String] 
normalStreamFunc (x:xs) = reverse(x) : normalStreamFunc xs 

effectfulStreamFunc :: [String] -> IO [String] 
effectfulStreamFunc [] = return [] 
effectfulStreamFunc (x:xs) = do 
    putStrLn x 
    rest <- effectfulStreamFunc xs 
    return (reverse(x):rest) 

main :: IO() 
main = do 
    let fns = ["a", "b", "c", "d"] 
    es <- effectfulStreamFunc fns 
    print $ show $ take 2 es 

Cập nhật:

Cảm ơn tất cả các bạn cho ý kiến ​​phản hồi hữu ích và chu đáo. Tôi đã không nhìn thấy các nhà điều hành sequence trước đây, đó là hữu ích để biết về. Tôi đã nghĩ đến một cách (ít thanh lịch hơn) để vượt qua các giá trị IO (String) thay vì Strings, nhưng đối với kiểu lập trình có tính hữu dụng hạn chế, vì tôi muốn các hàm stream khác hành động trên các chuỗi, chứ không phải trên hành động có thể tạo ra một chuỗi. Nhưng, dựa trên suy nghĩ thông qua các phản ứng khác, tôi nghĩ rằng tôi thấy lý do tại sao điều này là không thể giải quyết nói chung. Trong trường hợp đơn giản tôi đã trình bày, điều tôi thực sự muốn là toán tử sequence, vì tôi đã nghĩ rằng thứ tự luồng ngụ ý một thứ tự trên các hành động. Trong thực tế, không có thứ tự như vậy nhất thiết phải ngụ ý. Điều này trở nên rõ ràng hơn với tôi khi tôi nghĩ về một hàm luồng lấy hai luồng làm đầu vào (ví dụ: thêm hai lần hai luồng). Nếu cả hai luồng "đến" được thực hiện IO, thứ tự của các hành động IO đó là không xác định (trừ khi, tất nhiên, chúng ta định nghĩa nó bằng cách tự sắp xếp nó trong đơn nguyên IO). Đã giải quyết được vấn đề, cảm ơn tất cả!

Trả lời

7

Làm thế nào về mã này:

import IO 

normalStreamFunc :: [String] -> [String] 
normalStreamFunc (x:xs) = reverse(x) : normalStreamFunc xs 

effectfulStreamFunc :: [String] -> [IO (String)] 
effectfulStreamFunc [] = [] 
effectfulStreamFunc (x:xs) = 
    let rest = effectfulStreamFunc xs in 
     (putStrLn x >> return x) : rest 

main :: IO() 
main = do 
    let fns = ["a", "b", "c", "d"] 
    let appliedFns = effectfulStreamFunc fns 
    pieces <- sequence $ take 2 appliedFns 
    print $ show $ pieces 

Thay vì effectfulStreamFunc thực sự làm bất kỳ IO, chương trình này thay vì tạo ra một danh sách các hành động IO để thực hiện. (Lưu ý sự thay đổi kiểu chữ ký.) Chức năng chính sau đó mất 2 của những hành động, chạy chúng và in kết quả:

a 
b 
"[\"a\",\"b\"]" 

này hoạt động vì loại IO (String) chỉ là một chức năng/giá trị giống như bất kỳ khác mà bạn có thể đưa vào danh sách, đi qua, v.v. Lưu ý rằng cú pháp do không xảy ra trong "effectfulStreamFunc" - nó thực sự là một hàm thuần túy, mặc dù chữ "IO" trong chữ ký của nó. Chỉ khi chúng tôi chạy sequence đối với những người trong chính thì các hiệu ứng thực sự xảy ra.

+1

Nếu bạn vẫn thích làm ký hiệu, bạn có thể viết các mệnh đề thứ hai của effectfulStreamFunc như: effectfulStreamFunc (x: xs) = let putAction = do putStrLn x return x in putAction: effectfulStreamFunc xs Tôi nghĩ rằng nó đọc tốt hơn một chút. –

+0

Điểm tốt. Tôi đoán cú pháp làm là trực giao với vấn đề thực sự chạy monad. –

2

Tôi không thực sự hiểu mục tiêu chính của bạn nhưng việc bạn sử dụng kết quả putStrLn trong việc đánh giá toàn bộ danh sách vì nó sẽ đánh giá đối số khi được thực hiện. Hãy xem xét

import IO 

normalStreamFunc :: [String] -> [String] 
normalStreamFunc (x:xs) = reverse(x) : normalStreamFunc xs 

effectfulStreamFunc :: [String] -> IO [String] 
effectfulStreamFunc [] = return [] 
effectfulStreamFunc (x:xs) = do 
    rest <- effectfulStreamFunc xs 
    return (reverse(x):rest) 

main :: IO() 
main = do 
    let fns = ["a", "b", undefined,"c", "d"] 
    es <- effectfulStreamFunc fns 
    print $ show $ take 2 es 

kết quả này trong "[\" a \ ", \" b \ "]", trong khi sử dụng phiên bản putStrLn nó dẫn đến ngoại lệ.

1

Như đã đề cập bởi Tomh, bạn không thể thực sự làm điều này "an toàn", bởi vì bạn đang phá vỡ tính minh bạch tham chiếu trong Haskell.Bạn đang cố gắng thực hiện các tác dụng phụ một cách lười biếng, nhưng điều về sự lười biếng là bạn không được đảm bảo theo thứ tự hoặc liệu mọi thứ có được đánh giá hay không, vì vậy trong Haskell, khi bạn nói nó thực hiện một tác dụng phụ, nó luôn được thực hiện, và theo thứ tự chính xác được chỉ định. (ví dụ: trong trường hợp này, các tác dụng phụ từ cuộc gọi đệ quy là effectfulStreamFunc trước return, vì đó là thứ tự chúng được liệt kê) Bạn không thể làm điều này một cách lười biếng mà không sử dụng không an toàn.

Bạn có thể thử sử dụng một cái gì đó như unsafeInterleaveIO, đó là cách IO lười biếng (ví dụ: hGetContents) được triển khai trong Haskell, nhưng nó có vấn đề riêng; và bạn nói rằng bạn không muốn sử dụng nội dung "không an toàn".

import System.IO.Unsafe (unsafeInterleaveIO) 

effectfulStreamFunc :: [String] -> IO [String] 
effectfulStreamFunc [] = return [] 
effectfulStreamFunc (x:xs) = unsafeInterleaveIO $ do 
    putStrLn x 
    rest <- effectfulStreamFunc xs 
    return (reverse x : rest) 

main :: IO() 
main = do 
    let fns = ["a", "b", "c", "d"] 
    es <- effectfulStreamFunc fns 
    print $ show $ take 2 es 

Trong trường hợp này đầu ra trông như thế này

"a 
[\"a\"b 
,\"b\"]" 
Các vấn đề liên quan