2016-05-31 18 views
11

Hơi bị hoang mang bởi đoạn mã sau. Trong phiên bản không đồ chơi của vấn đề tôi đang cố gắng để làm một tính toán monadic trong một kết quả đơn nguyên, các giá trị trong đó chỉ có thể được xây dựng từ bên trong IO. Có vẻ như ma thuật đằng sau IO làm cho tính toán như vậy nghiêm ngặt, nhưng tôi không thể tìm ra chính xác điều đó xảy ra như thế nào.IO monad ngăn chặn ngắn mạch của bản đồ nhúngM?

Mã:

data Result a = Result a | Failure deriving (Show) 

instance Functor Result where 
    fmap f (Result a) = Result (f a) 
    fmap f Failure = Failure 

instance Applicative Result where 
    pure = return 
    (<*>) = ap 

instance Monad Result where 
    return = Result 
    Result a >>= f = f a 
    Failure >>= _ = Failure 

compute :: Int -> Result Int 
compute 3 = Failure 
compute x = traceShow x $ Result x 

compute2 :: Monad m => Int -> m (Result Int) 
compute2 3 = return Failure 
compute2 x = traceShow x $ return $ Result x 

compute3 :: Monad m => Int -> m (Result Int) 
compute3 = return . compute 

main :: IO() 
main = do 
    let results = mapM compute [1..5] 
    print $ results 
    results2 <- mapM compute2 [1..5] 
    print $ sequence results2 
    results3 <- mapM compute3 [1..5] 
    print $ sequence results3 
    let results2' = runIdentity $ mapM compute2 [1..5] 
    print $ sequence results2' 

Sản lượng:

1 
2 
Failure 
1 
2 
4 
5 
Failure 
1 
2 
Failure 
1 
2 
Failure 

Trả lời

10

trường hợp thử nghiệm Nice. Đây là những gì đang xảy ra:

  • trong mapM compute chúng ta thấy sự lười biếng trong công việc, như thường lệ. Không có gì ngạc nhiên ở đây.

  • trong mapM compute2 chúng tôi làm việc bên trong các đơn nguyên IO, có mapM định nghĩa sẽ yêu cầu toàn bộ danh sách: không giống như Result mà bỏ qua đuôi của danh sách càng sớm càng Failure được tìm thấy, IO sẽ luôn luôn quét toàn bộ danh sách. Lưu ý mã:

    compute2 x = traceShow x $ return $ Result x 
    

    Vì vậy, các bên trên sẽ in thông báo gỡ lỗi ngay khi mỗi phần tử trong danh sách tác vụ IO được truy cập. Tất cả là, vì vậy chúng tôi in tất cả mọi thứ.

  • trong mapM compute3 bây giờ chúng ta sử dụng, khoảng:

    compute3 x = return $ traceShow x $ Result x 
    

    Bây giờ, kể từ khi return trong IO là lười biếng, nó sẽ không kích hoạt các traceShow khi trả lại hành động IO. Vì vậy, khi mapM compute3 chạy, không có thông báo được xem. Thay vào đó, chúng tôi chỉ thấy thông báo khi sequence results3 được chạy, bắt buộc Result - không phải tất cả trong số chúng, nhưng chỉ khi cần thiết.

  • ví dụ cuối cùng Identity cũng khá phức tạp. Lưu ý này:

    > newtype Id1 a = Id1 a 
    > data Id2 a = Id2 a 
    > Id1 (trace "hey!" True) `seq` 42 
    hey! 
    42 
    > Id2 (trace "hey!" True) `seq` 42 
    42 
    

    khi sử dụng một newtype, khi chạy không có boxing/unboxing (AKA nâng) có liên quan, do đó buộc một giá trị Id1 x gây x được cưỡng bức. Với các loại data điều này không xảy ra: giá trị được bao bọc trong một hộp (ví dụ: Id2 undefined không tương đương với undefined).

    Trong ví dụ của bạn, bạn thêm một hàm tạo Identity, nhưng đó là từ newtype Identity !! Vì vậy, khi gọi

    return $ traceShow x $ Result x 
    

    các return đây không quấn bất cứ điều gì, và traceShow ngay lập tức được kích hoạt trong thời gian sớm mapM được chạy.

+0

Cảm ơn bạn rất nhiều vì câu trả lời của bạn, chi. Tôi có thể hỏi làm thế nào bạn biết rằng định nghĩa IO của mapM là nghiêm ngặt và trả lại là lười biếng? – NioBium

+3

@NioBium 'Failure >> = f = Failure' loại bỏ' f': không cần tiến xa hơn vào chuỗi monadic. IO có định nghĩa đòn bẩy thấp hơn không dễ viết, nhưng - ngoại lệ chặn - 'action >> = f' sẽ luôn gọi' f' vì người ta kỳ vọng rằng ví dụ 'action >> print 4' cuối cùng sẽ in 4 bất kể' action' làm gì (chặn các ngoại lệ IO và không kết thúc). – chi

+0

Phải. Cảm ơn một lần nữa! – NioBium

1

loại Result của bạn dường như hầu như giống hệt với Maybe, với

Result <-> Just 
Failure <-> Nothing 

Đối với lợi ích của não kém của tôi, tôi sẽ dính vào Maybe thuật ngữ trong phần còn lại của câu trả lời này.

chi giải thích lý do tại sao IO (Maybe a) không ngắn mạch theo cách bạn mong đợi. Nhưng có loại bạn có thể sử dụng cho loại điều này! Về cơ bản, đó là cùng loại, nhưng với một phiên bản Monad khác. Bạn có thể tìm thấy nó trong Control.Monad.Trans.Maybe. Nó trông giống như sau:

newtype MaybeT m a = MaybeT 
    { runMaybeT :: m (Maybe a) } 

Như bạn có thể thấy, đây chỉ là một wrapper newtype xung quanh m (Maybe a). Nhưng dụ Monad của nó là rất khác nhau:

instance Monad m => Monad (MaybeT m) where 
    return a = MaybeT $ return (Just a) 
    m >>= f = MaybeT $ do 
    mres <- runMaybeT m 
    case mres of 
     Nothing -> return Nothing 
     Just a -> runMaybeT (f a) 

Đó là, m >>= f chạy m tính toán trong đơn nguyên cơ bản, nhận Maybe một cái gì đó hoặc khác. Nếu nó được Nothing, nó chỉ dừng lại, trở về Nothing. Nếu nó được một cái gì đó, nó vượt qua đó để f và chạy kết quả. Bạn cũng có thể biến bất kỳ hành động m thành một "thành công" MaybeT m hành động sử dụng lift từ Control.Monad.Trans.Class:

class MonadTrans t where 
    lift :: Monad m => m a -> t m a 

instance MonadTrans MaybeT where 
    lift m = MaybeT $ Just <$> m 

Bạn cũng có thể sử dụng lớp này, được xác định ở đâu đó như Control.Monad.IO.Class, mà thường là rõ ràng hơn và có thể là nhiều thuận tiện hơn:

class MonadIO m where 
    liftIO :: IO a -> m a 

instance MonadIO IO where 
    liftIO m = m 

instance MonadIO m => MonadIO (MaybeT m) where 
    liftIO m = lift (liftIO m) 
Các vấn đề liên quan