2

I have a Monad of named TaskMonad, defined as follows:

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

where Environment is a record type and TaskResult is an ADT; but they are not important for the problem.

I have defed Functor, Applicative and Monad instances for TaskMonad, and I now want to be able to combine this monad with other Monads (e.g. IO), So I defined a new type as follows:

newtype Task m a = Task { runTask :: m (TaskMonad a) }

I have defined Functor and Applicative as follows:

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)

And I also made Task member of the MonadTrans class:

instance MonadTrans Task where
    lift = Task . (liftM return)

So far so good (or atleast it compiles..), but now I want to define the instance for Monad, but I am running into problems here:

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

I attempted multiple things, most attempts starting out like this:

(>>=) ta tb = Task $ do tma <- runTask ta 

Now we have tma :: TaskMonad a inside the do block for the m monad. Now what I would like to do, is somehow calling the >>= instance for TaskMonad so I can get the result of tma, a value of type a so I can parameterize tb with it to obtain a value of Task b. But I am within the context of the m monad and I'm running into all kinds of problems.

How could I obtain tma's result to provide it to tb?

Jasper
  • 66
  • 3
  • 3
    an obvious problem will be that you don't get an `Environment` right? I think you have to break open `TaskMonad` again (instead of using the `>>=` you already got) – Random Dev Apr 25 '16 at 12:42
  • Uh oh, after many bad words spoken I got my instance of Applicative for TaskMonad to work. I'm getting rusty. Trying to get your bind to work now. – Bartek Banachewicz Apr 25 '16 at 13:06
  • Hmm, I guess I will have to fiddle with the `Environment` a bit indeed. Important to note is that the `(>>=)` function for `TaskMonad` is somewhat complex; simply providing an `Environment` to a `TaskMonad` is not sufficient. I need it to be expressed using `TaskMonad`'s `(>>=)`. Thanks for trying Bartek! I'm out of bad words myself by now. – Jasper Apr 25 '16 at 13:16
  • 2
    Maybe it would make sense to define `TaskMonad` as a transformer from the very beginning? It looks similar to StateT on the outside, perhaps this is the way to go? – Bartek Banachewicz Apr 25 '16 at 13:25

2 Answers2

2

Okay, I don't know how much this helps, but if you actually start with a transformer from day 0 (in TaskMonad), the way you can do it is:

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

Probably there's also a way to do that the way you originally intended, but I am pretty sure original Task would also need to be changed to take initial Environment.

I presume you're actually doing more than State in your monad, so that would need to be put in respective instances, but I think the framework for that should help.

And of course, shall you ever need to use a non-transformer version, just pass in Identity for m.


Disclaimer:

I know this implemenation of Applicative instance doesn't make sense, but I was building that on old GHC w/o ApplicativeDo and it was literally the easiest thing to put the silly constraint there.

Community
  • 1
  • 1
Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135
2

As described in @BartekBanachewicz's answer, putting the monad m inside -> is the way to go.

I believe it's not possible to do it the way you want by having m (TaskMonad a), at least not generically. In general monads aren't closed under composition and this is an example of such a situation.

Let me give a simplified example (some theory will be required for it): Let's work with the reader monad instead of the state monad, let's drop TaskResult and let's have the environment as a type parameter. So TaskMonad will be just m (r -> a). Now let's assume it's a monad, then there is

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

Specializing a to Void (see also Bottom type) and m to Either r we get

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

But then we're able to construct

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

as Right Left :: Either r (r -> Either r (r -> Void)). Through Curry-Howard isomorphism this would mean that we'd be able to prove Double negation elimination in intuitionistic logic, which is a contradiction.

Your situation is somewhat more complex, but a similar argument could be made there too. The only hole there is that we assumed that the "environment" part, r, was generic, so won't work if your join or >>= is somehow specific for Environment. So you might be able to do it in such a case, but my guess is you'll then encounter other problems preventing you to get a proper non-trivial Monad instance.

Community
  • 1
  • 1
Petr
  • 62,528
  • 13
  • 153
  • 317