[英]Values inside monads, nested in data structures?

Suppose that in a Haskell program I have some data whose type is something like:


  • IO [ IO (Int, String, Int) ], or
  • IO [IO(Int,String,Int)]或

  • IO [ (Int, String, IO Int) ], or
  • IO [(Int,String,IO Int)]或

  • [ (Int, String, IO Int) ]
  • [(Int,String,IO Int)]

but I have pure functions that should operate on [ (Int, String, Int) ]. It seems that I'd have to clumsily remove the inside values from the IO monad, until I got something like IO [ (Int, string, Int) ] and then (from inside the IO monad) apply the pure functions. There is no easy pre-defined way to do this, I suppose? Something that would lift a whole data structure into a monad, turning all inside types into pure types? (That would be very convenient!)

但我有純函數,應該在[(Int,String,Int)]上運行。似乎我必須笨拙地從IO monad中刪除內部值,直到我得到類似IO [(Int,string,Int)]然后(從IO monad內部)應用純函數。我想,沒有簡單的預定義方法可以做到這一點?將整個數據結構提升為monad的東西,將所有內部類型轉換為純類型? (那會很方便!)

2 个解决方案


You could use the liftM* function from the Control.Monad module, or the liftA* functions for applicatives.

您可以使用Control.Monad模塊中的liftM *功能,或者應用程序的liftA *功能。

liftM allows you to lift a pure function to work inside a Monad, e.g.:


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

This way you don't have to manually write things like "s >>= \x -> return (reverse x)" everywhere.

這樣你就不必手動編寫像“s >> = \ x - > return(reverse x)”這樣的東西。

Although, this won't help you with your [(String, Int, IO Int)] example, if the pure function you have deals with a [(String, Int, Int)]. Since the third element in the tuple really isn't an Int.

雖然,這對您的[(String,Int,IO Int)]示例沒有幫助,如果您使用的純函數處理[(String,Int,Int)]。由於元組中的第三個元素確實不是Int。

In that case I'd suggest to first write a function [(String, Int, IO Int)] -> IO [(String, Int, Int)] and that apply the lifted pure function.

在那種情況下,我建議先寫一個函數[(String,Int,IO Int)] - > IO [(String,Int,Int)]並應用提升的純函數。

This is the most general function I could come up with to do this:


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

You can call it like so:


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

This function will only work if you have a single monad that's somewhere deep in a type. If you have multiple, I think you should really be thinking about the type your working in with and see if you can't make it simpler.



First some usage example for the solution below called reduce (unless you suggest a better name):


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

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

> reduce [("ab", "12"), ("cd", "3")] :: [(Char, Char)]

Your example is also solved with it:


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

And the implementation of 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)



粤ICP备14056181号  © 2014-2021