If I define the "bind" function like this:
(>>=) :: M a -> (a -> M' b) -> M' b
Will this definition help me if I want the result to be of a new Monad type, or I should use same Monad but with b in the same Monad box as before?
If I define the "bind" function like this:
(>>=) :: M a -> (a -> M' b) -> M' b
Will this definition help me if I want the result to be of a new Monad type, or I should use same Monad but with b in the same Monad box as before?
Not only will that definition not help, but it will seriously confuse future readers of your code, since it will break all expectations of use for it.
For instance, are both M and M' supposed to be Monads? If so, then how are they defined? Remember: the definition of >>=
is part of the definition of Monad, and is used everywhere to define other Monad-using functions - every function besides return
and fail
themselves.
Also, do you get to choose which M and M' you use, or does the computer? If so, then how do you choose? Does it work for any two Monad instances, or is there some subset of Monad that you want - or does the choice of M determine the choice of M'?
It's possible to make a function like what you've written, but it surely is a lot more complicated than >>=
, and it would be misleading, cruel, and potentially disastrous to try to cram your function into >>=
's clothes.
You may want to look at this sample from Oleg: http://okmij.org/ftp/Computation/monads.html#param-monad
This can be a complicated thing to do, but it is doable in some contexts. Basically, if they are monads you can see inside (such as Maybe
or a monad you've written) then you can define such an operation.
One thing which is sometimes quite handy (in GHC) is to replace the Monad
class with one of your own. If you define return, >>=, fail
you'll still be able to use do
notation. Here's an example that may be like what you want:
class Compose s t where
type Comp s t
class Monad m where
return :: a -> m s a
fail :: String -> m a
(>>=) :: (Compose s t) => m s a -> (a -> m t b) -> m (Comp s t) b
(>>) :: (Compose s t) => m s a -> m t b -> m (Comp s t) b
m >> m' = m >>= \_ -> m'
You can then control which types can be sequenced using the bind operator based on which instances of Compose
you define. Naturally you'll often want Comp s s = s
, but you can also use this to define all sorts of crazy things.
For instance, perhaps you have some operations in your monad which absolutely cannot be followed by any other operations. Want to enforce that statically? Define an empty datatype data Terminal
and provide no instances of Compose Terminal t
.
This approach is not good for transposing from (say) Maybe
to IO
, but it can be used to carry along some type-level data about what you're doing.
If you really do want to change monads, you can modify the class definitions above into something like
class Compose m n where
type Comp m n
(>>=*) :: m a -> (a -> n b) -> (Compose m n) b
class Monad m where
return :: a -> m a
fail :: String -> m a
(>>=) :: Compose m n => m a -> (a -> n b) -> (Compose m n) b
m >>= f = m >>=* f
(>>) :: Compose m n => m a -> (n b) -> (Compose m n) b
m >> n = m >>=* \_ -> n
I've used the former style to useful ends, though I imagine that this latter idea may also be useful in certain contexts.