Monad Transformer:由于不同的monad而导致定义绑定的麻烦

时间:2016-04-25 12:34:13

标签: haskell functional-programming monads monad-transformers

我有一个名为TaskMonad的Monad,定义如下:

data TaskMonad a = TaskMonad (Environment -> (TaskResult a, Environment))

其中Environment是记录类型,TaskResult是ADT;但它们对这个问题并不重要。

我已为Functor定义了ApplicativeMonadTaskMonad个实例,我现在希望能够将此monad与其他Monad结合使用(例如{{1}所以我定义了一个新类型如下:

IO

我已定义newtype Task m a = Task { runTask :: m (TaskMonad a) } Functor如下:

Applicative

我还让instance Monad m => Functor (Task m) where fmap f ta = Task $ do tma <- runTask ta return (fmap f tma) instance Monad m => Applicative (Task m) where pure = Task . return . return (<*>) prod tx = Task $ do tmprod <- runTask prod tmtx <- runTask tx return (tmprod <*> tmtx) 班的Task成员:

MonadTrans

到目前为止一直很好(或至少它编译..),但现在我想为instance MonadTrans Task where lift = Task . (liftM return) 定义实例,但我遇到了问题:

Monad

我尝试了多种方法,大多数尝试都是这样开始的:

instance Monad m => Monad (Task m) where
    return      = pure
    (>>=) ta tb = ...

现在,我们(>>=) ta tb = Task $ do tma <- runTask ta tma :: TaskMonad a内有do monad。现在我想做的是,以某种方式调用m的{​​{1}}实例,这样我就可以获得>>=的结果,类型TaskMonad的值,这样我就可以参数化了tma用它来获取值a。但我在tb monad的背景下,我遇到了各种各样的问题。

如何获取Task b的结果以将其提供给m

2 个答案:

答案 0 :(得分:2)

好吧,我不知道这有多大帮助,但是如果你真的从第0天开始使用变压器(TaskMonad),你可以这样做:

data TaskMonad m a = TaskMonad (Environment -> m (TaskResult a, Environment)) deriving Functor

instance Monad m => Monad (TaskMonad m) where
    return = pure
    (TaskMonad f) >>= b = TaskMonad $ \e -> do
        (TaskResult r, e') <- f e
        let (TaskMonad g) = b r
        g e'

instance (Monad m, Functor m) => Applicative (TaskMonad m) where
    pure a = TaskMonad $ \e -> return (TaskResult a, e)
    (TaskMonad f) <*> (TaskMonad g) = TaskMonad $ \e -> do
        (TaskResult f', e') <- f e 
        (TaskResult a, e'') <- g e'
        return (TaskResult (f' a), e'')

可能还有一种方法可以按照您最初的预期方式执行此操作,但我非常确定原始Task也需要更改为初始Environment

我认为你在monad中实际上做的不仅仅是State,所以需要将它放在各自的实例中,但我认为这个框架应该会有所帮助。

当然,您是否需要使用非变压器版本,只需将Identity传递给m

声明:

我知道Applicative实例的实现没有意义,但是我在没有ApplicativeDo的旧GHC上构建它,实际上最简单的做法就是设置傻约束那里。

答案 1 :(得分:2)

如@ BartekBanachewicz的回答所述,将monad m置于->内是可行的方法。

我认为不可能通过m (TaskMonad a)以你想要的方式去做,至少不是一般的。一般来说monads aren't closed under composition就是这种情况的一个例子。

让我举一个简化的例子(它需要一些理论):让我们使用读者monad而不是状态monad,让我们放弃TaskResult并让环境作为类型参数。因此TaskMonad只是m (r -> a)。现在让我们假设它是一个单子,然后有

join :: m (r -> (m (r -> a))) -> m (r -> a)

a专门设为Void(另请参阅Bottom type)和mEither r

join :: Either r (r -> (Either r (r -> Void))) -> Either r (r -> Void)

但是我们能够构建

doubleNegationElimination :: Either r (r -> Void)
doubleNegationElimination = join (Right Left)

Right Left :: Either r (r -> Either r (r -> Void))。通过Curry-Howard isomorphism这意味着我们可以证明Double negation elimination  在直觉主义逻辑中,这是一个矛盾。

你的情况有点复杂,但也可以在那里提出类似的论点。唯一的漏洞是,我们假设“环境”部分r是通用的,因此如果您的join>>=以某种方式特定于{{1},则无效}。所以你可以在这种情况下做到这一点,但我猜你会遇到其他问题阻止你得到一个正确的非平凡的Environment实例。