monad中的值,嵌套在數據結構中?

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


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

假設在Haskell程序中我有一些類型如下的數據:

  • 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 个解决方案

#1


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.:

liftM允許你舉起一個純函數在Monad中工作,例如:

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

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.

只有當你有一個類型很深的monad時,這個函數才有效。如果你有多個,我認為你應該考慮你所使用的類型,看看你是否能夠簡化它。

#2


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

首先,下面的解決方案的一些用法示例稱為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')]

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:

並執行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)

注意!

本站翻译的文章,版权归属于本站,未经许可禁止转摘,转摘请注明本文地址:https://www.itdaan.com/blog/2009/07/21/725c3134147486ab3e4683f726ad1286.html



 
粤ICP备14056181号  © 2014-2021 ITdaan.com