2009-07-21 25 views
5

Giả sử rằng trong một chương trình Haskell Tôi có một số dữ liệu có loại là cái gì đó như:Giá trị bên trong monads, được lồng trong cấu trúc dữ liệu?

  • IO [ IO (Int, String, Int) ], hoặc
  • IO [ (Int, String, IO Int) ], hoặc
  • [ (Int, String, IO Int) ]

nhưng tôi có chức năng thuần túy mà nên hoạt động trên [ (Int, String, Int) ]. Dường như tôi sẽ phải loại bỏ một cách vụng về các giá trị bên trong từ đơn nguyên IO, cho đến khi tôi nhận được một cái gì đó như IO [(Int, string, Int)] và sau đó (từ bên trong đơn nguyên IO) áp dụng các hàm thuần túy. Không có cách dễ dàng xác định trước để làm điều này, tôi cho rằng? Cái gì đó sẽ nâng toàn bộ cấu trúc dữ liệu thành một đơn nguyên, biến tất cả các loại bên trong thành các loại thuần túy? (Điều đó rất thuận tiện!)

+1

Cảm ơn các bạn đã có câu trả lời tuyệt vời! Bạn hoàn toàn hữu ích! – Jay

Trả lời

6

Bạn có thể sử dụng chức năng liftM* từ mô-đun Control.Monad hoặc chức năng liftA* cho applicatives.

liftM cho phép bạn nhấc một hàm thuần túy để làm việc bên trong một đơn nguyên, ví dụ .:

ghci> let s = return "Hello" :: IO String 
ghci> liftM reverse s 
"olleH" 

Bằng cách này bạn không cần phải tự viết những câu như "s >>= \x -> return (reverse x)" ở khắp mọi nơi.

Mặc dù, điều này sẽ không giúp bạn với ví dụ [(String, Int, IO Int)] của bạn, nếu hàm thuần túy bạn có giao dịch với [(String, Int, Int)]. Vì phần tử thứ ba trong tuple thực sự không phải là Int.

Trong trường hợp đó, tôi đề nghị đầu tiên viết hàm [(String, Int, IO Int)] -> IO [(String, Int, Int)] và áp dụng chức năng thuần túy được nâng lên.


Đây là chức năng chung nhất mà tôi có thể đưa ra để làm điều này:

conv :: Monad m => (f (m a) -> m (f a)) -> [f (m a)] -> m [f a] 
conv f = sequence . map f 

Bạn có thể gọi nó như vậy:

liftTrd :: Monad m => (a, b, m c) -> m (a, b, c) 
liftTrd (x, y, mz) = mz >>= \z -> return (x, y, z) 

conv liftTrd [("hi", 4, return 2)] :: IO [(String, Int, Int)] 

Chức năng này sẽ chỉ làm việc nếu bạn có một đơn lẻ ở đâu đó sâu trong một loại. Nếu bạn có nhiều, tôi nghĩ bạn nên thực sự suy nghĩ về loại hình của bạn làm việc với và xem nếu bạn không thể làm cho nó đơn giản hơn.

+0

Thật thú vị! Có lẽ ngôn ngữ nên có thứ gì đó như thế này được tích hợp sẵn?Cái gì đó sẽ làm việc cho tất cả các loại (nghĩ về một danh sách * bên * một tuple, ví dụ - hoặc các loại fata đại số ...) – Jay

+0

By the way ... Sử dụng chuỗi sẽ có nghĩa là tôi không thể sử dụng nó trên danh sách vô hạn , đúng? – Jay

+0

@ Jay: Có lẽ một cái gì đó có thể được thực hiện với 'unsafeInterleaveIO', nhưng thực sự 'sequence' trên một danh sách vô hạn mất một thời gian khá dài. – ephemient

4

Đầu tiên một số ví dụ sử dụng cho các giải pháp dưới đây gọi reduce (trừ khi bạn đề nghị một tên tốt hơn):

> reduce [(["ab", "c"], "12")] :: [(String, String)] 
[("ab","12"),("c","12")] 

> reduce [(["ab", "c"], "12")] :: [(Char, Char)] 
[('a','1'),('a','2'),('b','1'),('b','2'),('c','1'),('c','2')] 

> reduce [("ab", "12"), ("cd", "3")] :: [(Char, Char)] 
[('a','1'),('a','2'),('b','1'),('b','2'),('c','3'),('d','3')] 

dụ của bạn cũng được giải quyết với nó:

complexReduce :: Monad m => m (m (a, b, m [m (c, m d)])) -> m (a, b, [(c, d)]) 
complexReduce = reduce 

Và việc thực hiện reduce :

{-# LANGUAGE FlexibleContexts, FlexibleInstances, IncoherentInstances, MultiParamTypeClasses, UndecidableInstances #-} 

import Control.Monad 

-- reduce reduces types to simpler types, 
-- when the reduction is in one of the following forms: 
-- * make a Monad disappear, like join 
-- * move a Monad out, like sequence 
-- the whole magic of Reduce is all in its instances 
class Reduce s d where 
    reduce :: s -> d 

-- Box is used only for DRY in Reduce instance definitions. 
-- Without it we, a Reduce instance would need 
-- to be tripled for each variable: 
-- Once for a pure value, once for a monadic value, 
-- and once for a reducable value 
newtype Box a = Box { runBox :: a } 
instance Monad m => Reduce (Box a) (m a) where 
    reduce = return . runBox 
instance Reduce a b => Reduce (Box a) b where 
    reduce = reduce . runBox 
redBox :: Reduce (Box a) b => a -> b 
redBox = reduce . Box 

-- we can join 
instance (Monad m 
    , Reduce (Box a) (m b) 
) => Reduce (m a) (m b) where 
    reduce = join . liftM redBox 

-- we can sequence 
-- * instance isnt "Reduce [a] (m [b])" so type is always reduced, 
-- and thus we avoid overlapping instances. 
-- * we cant make it general for any Traversable because then 
-- the type system wont find the right patterns. 
instance (Monad m 
    , Reduce (Box a) (m b) 
) => Reduce (m [a]) (m [b]) where 
    reduce = join . liftM (sequence . fmap redBox) 

instance (Monad m 
    , Reduce (Box a) (m c) 
    , Reduce (Box b) (m d) 
) => Reduce (a, b) (m (c, d)) where 
    reduce (a, b) = liftM2 (,) (redBox a) (redBox b) 

instance (Monad m 
    , Reduce (Box a) (m d) 
    , Reduce (Box b) (m e) 
    , Reduce (Box c) (m f) 
) => Reduce (a, b, c) (m (d, e, f)) where 
    reduce (a, b, c) = 
    liftM3 (,,) (redBox a) (redBox b) (redBox c) 
Các vấn đề liên quan