重新包装monad - 任何通用方式?

时间:2012-03-13 12:51:26

标签: haskell monads

鉴于两个monad Monad mMonad n,我想将m (n a)转换为n (m a)。但似乎没有通用的方法,因为(>>=)return只处理一个monad类型,虽然(>>=)允许从monad中提取内容,但你必须将它们打包回来monad类型因此它可以是结果值。

但是,如果我们将m设置为固定类型,则工作变得简单。以Maybe为例:

reorder :: (Monad n) => Maybe (n a) -> n (Maybe a)
reorder Nothing = return Nothing
reorder (Just x) = do
    x' <- x
    return $ Just x'

或列表:

reorder :: (Monad n) => [n a] -> n [a]
reorder [] = return []
reorder (x:xs) = do
    x'  <- x
    xs' <- reorder xs
    return (x':xs')

不难看出,我们这里有一个模式。更明显的是,以Applicative方式编写它,它只不过是将数据构造函数应用于每个元素:

reorder (Just x) = Just <$> x
reorder (x:xs) = (:) <$> x <*> (reorder xs)

我的问题是:是否已经存在用于描述此类操作的haskell类型类,或者我是否必须自己发明轮子?

我在GHC文档中进行了简短的搜索,发现对此主题没什么用处。

4 个答案:

答案 0 :(得分:14)

Data.Traversable提供您正在寻找的内容:

sequenceA :: (Traversable t, Applicative f) => t (f a) -> f (t a)

GHC甚至为自动派生实例提供支持:

{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}
import Data.Foldable
import Data.Traversable

data List a = Nil | Cons a (List a) 
  deriving(Functor, Foldable, Traversable)

答案 1 :(得分:5)

(Monad m, Monad n) => m (n a) -> n (m a)的快速搜索显示,有几个功能(大致)符合您正在寻找的签名:

  1. Data.Traversable.sequence :: (Traversable t, Monad m) => t (m a) -> m (t a);
  2. Data.Traversable.sequenceA :: Applicative f => t (f a) -> f (t a);
  3. Contro.Monad.sequence :: Monad m => [m a] -> m [a](也由Prelude导出。)
  4. [a]Maybe a都是可遍历的实例,因此您的重新排序功能只是Data.Traversable.sequence的应用程序。人们可以写一下,例如:

    ghci> (Data.Traversable.sequence $ Just (return 1)) :: IO (Maybe Int)
    Just 1
    it :: Maybe Int
    
    ghci> (Data.Traversable.sequence $ Just ([1])) :: [Maybe Int]
    [Just 1]
    it :: [Maybe Int]
    
    ghci> (Data.Traversable.sequence $ [Just 1]) :: Maybe [Int]
    Just [1]
    it :: Maybe [Int]
    

    但是请注意,具体的类声明是class (Functor t, Foldable t) => Traversable t,如果你再看一下其他两个函数的类型,那么看起来你可能无法在通用中完成它所有monad mn没有限制/先决条件的方式。

答案 2 :(得分:4)

一般情况下不能这样做:一个不能做到这一点的monad的好例子是读者(或函数)monad。这将需要以下功能:

impossible :: (r -> IO a) -> IO (r -> a)

证明函数不能实现并不是直截了当的。但直观地说,问题在于,在我们知道IO参数是之前,必须在返回的值中完成r所做的。因此impossible readFile在知道要打开哪些文件之前必须生成纯函数FilePath -> String。显然,至少impossible不能做你想要的。

答案 3 :(得分:1)

并非所有Monads都能以这种方式通勤。 Edward Kmett的distributive包为类型构造函数提供了一个类型Distributive,类似于你想要的(简化):

class Functor g => Distributive g where
  distribute  :: Functor f => f (g a) -> g (f a)
  collect     :: Functor f => (a -> g b) -> f a -> g (f b)

distributecollect提供了彼此相对的默认定义。我在searching hayoo for the desired type signature找到了这个包。