2009-07-21 17 views
5

Haskellのプログラムの中で、私はそのタイプのようなものであるいくつかのデータがあるとします。データ構造にネストされたモナド内部の値は?

  • IO [ IO (Int, String, Int) ]、または
  • IO [ (Int, String, IO Int) ]、または
  • [ (Int, String, IO Int) ]

を私はそのはず、純粋な機能を持っています[ (Int, String, Int) ]で動作します。 IO([Int、string、Int)]のようなものが得られ、その後(IOモナドの内部から)純粋な関数を適用するまで、IOモナドの内部値を不器用に取り除かなければならないようです。これを行うための簡単な事前定義された方法はありません。 タイプのすべてを純粋なタイプに変換して、全体のデータ構造をモナドに持ち上げる何か?

+1

素晴らしい回答をいただきありがとうございました!あなたは絶対に助けになった! – Jay

答えて

6

Control.MonadモジュールのliftM*機能、またはapplicativesliftA*機能を使用できます(これは非常に便利です)。

liftMあなたが例えばモナド、内部で動作するように、純粋な機能を持ち上げることができます。:

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

手動でどこでも「s >>= \x -> return (reverse x)」のようなものを記述する必要はありませんこの方法です。

純粋な機能が[(String, Int, Int)]の場合は、[(String, Int, IO Int)]の例では役に立ちませんが、タプルの3番目の要素は実際にはIntではないためです。

その場合、私は最初に関数[(String, Int, IO Int)] -> IO [(String, Int, Int)]を書くことをお勧めします。これは持ち上げられた純関数を適用します。


これは私がこれを行うに思い付くことができる最も一般的な機能である:

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

そうようにあなたがそれを呼び出すことができます。あなたの場合

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)] 

この機能はのみ機能しますある種の深いところにある単一のモナドを持っている。複数の人がいる場合は、実際に作業しているタイプについて考えていて、簡単にできないかどうかを確認する必要があります。

+0

それは面白いです! 言語には、このようなものが組み込まれている必要がありますか?すべての型のために働くもの(リスト*タプル、例えば代数的な型のリストを考えてみてください) – Jay

+0

ところで...シーケンスを使うと、無限リストで使うことができなくなります、 右? – Jay

+0

@ Jay:おそらく 'unsafeInterleaveIO'で何かできるのですが、無限リストの' sequence'はかなり長い時間がかかります。 – ephemient

4

reduceと呼ばれる以下のソリューションのための最初にいくつかの使用例(あなたがより良い名前を示唆していない限り):

> 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')] 

あなたの例もそれで解決されています

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

そして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) 
関連する問題