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 モナドの内部から) 純粋な関数を適用する必要があるようです。これを行うための事前に定義された簡単な方法はありません。データ構造全体をモナドに持ち上げ、内部のすべての型を純粋な型に変えるものはありますか? (それはとても便利です!)

4

2 に答える 2

6

Control.Monadモジュールの関数、または applicatives のliftM*関数を使用できます。liftA*

liftMモナド内で動作するように純粋な関数を持ち上げることができます。例:

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

s >>= \x -> return (reverse x)このようにして、どこにでも" " のようなものを手動で書く必要はありません。

[(String, Int, IO Int)]ただし、純粋な関数が を扱っている場合、これはあなたの例では役に立ちません[(String, Int, 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)]

この関数は、型のどこかに深い単一のモナドがある場合にのみ機能します。複数ある場合は、作業しているタイプについて本当に考えて、それを単純化できないかどうかを確認する必要があると思います.

于 2009-07-21T18:07:04.473 に答える
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)
于 2009-07-21T20:50:00.590 に答える