2011-12-16 33 views
15

Do sau:Trích xuất một giá trị Có lẽ trong IO

> (liftM2 fromMaybe) (ioError $ userError "OOPS") (return $ Just "ok") 

ghci mang lại cho tôi

*** Exception: user error (OOPS) 

Tất nhiên, fromMaybe đang làm việc một cách chính xác:

> (liftM2 fromMaybe) (return $ "not me") (return $ Just "ok") 
"ok" 

Nhưng dường như Thao tác IO đang được thực hiện và sau đó bị hủy:

> (liftM2 fromMaybe) (putStrLn "computing.." >> "discarded") (return $ Just "ok") 
computing.. 
"ok" 

Tại sao điều này lại xảy ra? Có cách nào để làm cho IO monad lazier?

Cụ thể, được đưa ra value :: IO (Maybe a) một (sạch, súc tích) cách để nói

result <- (liftM2 fromMaybe) err value 

là những gì và có nó giải nén dẫn hoặc ném một IOError cho phù hợp?

Trả lời

12

Tôi không biết rằng việc tạo IO lazier là đúng hướng ở đây. Những gì bạn dường như muốn làm là lần đầu tiên có được tại Maybe, sau đó loại bỏ nó. Điều này có thể được viết theo nhiều cách, đây là một lựa chọn:

test :: IO (Maybe a) -> IO a 
test = (>>= maybe (ioError $ userError "oops") return) 
10

Nếu bạn dịch từ liftM2 làm-ký hiệu, nó rõ ràng lý do tại sao mã của bạn thất bại:

do x <- ioError $ userError "OOPS" 
    y <- return $ Just "ok" 
    return $ fromMaybe x y 

này sẽ không bao giờ đi qua dòng đầu tiên , vì nó vô tình ném một ngoại lệ.

Anthony's suggestion sẽ làm việc tốt, nhưng nếu bạn không quan tâm đến những ngoại lệ cụ thể ném, bạn cũng có thể sử dụng mô hình phù hợp:

do Just result <- value 

Nếu mô hình không phù hợp, điều này sẽ gọi fail, mà trong trường hợp của IO đơn vị ném một ngoại lệ.

> Just x <- return Nothing 
*** Exception: user error (Pattern match failure in do expression at <interactive>:1:0-5) 
5

một (sạch, súc tích) cách để ... giải nén [các] kết quả hoặc ném một IOError phù hợp là gì?

Tôi khuyên bạn nên tránh dựa vào các lỗi ném. Thay vào đó, hãy xử lý "lỗi" một cách rõ ràng:

maybeM :: Monad m => m b -> (a -> m b) -> m (Maybe a) -> m b 
maybeM err f value = do 
    x <- value 
    case x of 
    Just y -> f y 
    Nothing -> err 

-- This can be written simply as: 
maybeM err f value = do 
    x <- value 
    maybe err f x 

-- or even shorter! This is starting to look like Anthony's answer :) 
maybeM err f value = value >>= maybe err f 

Đầu vào và loại của hàm phải tự nói lên. Bạn sử dụng nó bằng cách cho nó một hành động để thực hiện cho trường hợp Nothing hoặc một hàm để thực hiện trên giá trị bên trong cho trường hợp Just.Đối với đầu vào cụ thể của bạn này sẽ như thế nào:

maybeM (ioError $ userError "OOPS") return (return $ Just "ok") 

Vì vậy, nếu bạn hoàn toàn phải, sau đó là "cách ngắn gọn để giải nén kết quả hoặc ném một IOError" sẽ là:

-- compare to fromJust, a function to be avoided 
fromJustIO :: IO (Maybe a) -> IO a 
fromJustIO = maybeM (ioError $ userError "OOPS") return 

Thông báo như thế nào chữ ký loại cho điều này là thực tế Maybe a -> a, đó là bản chất của magicMonadUnwrap :: Monad m => m a -> a, nên đặt ra một số cờ đỏ. Tuy nhiên, bạn có thể sử dụng tính tàn bạo này theo cách đơn giản:

result <- fromJustIO value 

Mặc dù một lần nữa, tôi không khuyến khích sử dụng ngoại lệ tại đây. Hãy thử xử lý lỗi một cách thanh lịch hơn là chỉ đơn giản là phát nổ, bằng cách sử dụng maybeM và cung cấp một hành động IO để thực thi trong trường hợp lỗi.

+0

Cảm ơn bạn đã quan tâm, nhưng đó là một chút không có cơ sở. Tôi đang ném các lỗi trong một số chức năng cấp hai mà mỗi người làm chút IO của họ để lấy một số cấu hình. Chức năng cấu hình tầng đầu tiên sẽ thử tất cả chúng với một "bắt" và định dạng bất kỳ lỗi nào một cách độc đáo. Nó cho phép tôi duy trì việc xử lý lỗi của mình ở một nơi mà không cần chỉ định một hàm "handleErr" thông qua tất cả các hàm hạng hai. Trong kịch bản như vậy, các lỗi vẫn là một ý tưởng tồi? – So8res

+4

So8res: cá nhân tôi muốn có các hàm bậc hai tạo ra các giá trị 'Có thể', và sau đó hàm cấu hình tầng đầu tiên có thể gộp chúng lại với nhau như' sequence' hoặc '<$>' và '<*>', hoặc xử lý mỗi lỗi tiềm năng riêng biệt, như mong muốn. Đó là một vấn đề của hương vị, nhưng theo ý kiến ​​của tôi, lỗi gần như luôn luôn là một ý tưởng tồi trong Haskell, kể từ khi chúng tôi có abstractions mạnh mẽ cho phép chúng tôi soạn 'Có lẽ'. Cơ chế lưu lượng điều khiển ném/nắm không khớp với kiểu FP mà Haskell khuyến khích. –

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