not possible with monad transformers ?

Hi, I have been playing with Maybe and Output monad transformers and realized that I cannot make the following work nicely:
foo a b = do output "before" r <- (liftM2(+)) a b when r == Nothing $ output "error" return r
As soon as computation produces Nothing, I am loosing ability to do any output. I think what I need is not wrapping one monad into another with transformer; I need some sort of monad combiner:
newtype MC m1 m2 = MC (m1 a, m2 b)
So I could execute both of monads ‘in parallel’. But I have no idea how to express this or even if this is doable at all. Any comments? Thanks, Pavel. P.S. Here is summary of what I tried with transformers so far: Let’s say I have following monad transformers:
newtype MaybeMonadT m a = MMT (m a)
newtype OuptutMonadT m o a = OMT (m a, o)
And I am trying to use following monads:
type M1 a = MaybeMonadT OutputM a type M2 a = OuptutMonadT Maybe String a
Now, they both won’t quite work in the following example:
foo a b = do output "before" r <- (liftM2(+)) a b when r == Nothing $ output "error" return r
In case of M1, as soon as I get Nothing in r, computation will stop and return without any output gathered. In case of M2, as soon as I get Nothing in r, computation will stop and return only with output gathered so far. That is
output "error" will never be called.

This:
foo a b = do
output "before"
let r = liftM2 (+) a b
when (r == Nothing) $ output "error"
return r -- ??? "lift r"
seems to address your complains, but I don't understand what you want
well enough to know if this is that.
mike
Pavel Zolnikov
Hi, I have been playing with Maybe and Output monad transformers and realized that I cannot make the following work nicely:
foo a b = do output "before" r <- (liftM2(+)) a b when r == Nothing $ output "error" return r
As soon as computation produces Nothing, I am loosing ability to do any output. I think what I need is not wrapping one monad into another with transformer; I need some sort of monad combiner:
newtype MC m1 m2 = MC (m1 a, m2 b)
So I could execute both of monads âin parallelâ. But I have no idea how to express this or even if this is doable at all. Any comments?
Thanks, Pavel.
P.S. Here is summary of what I tried with transformers so far:
Letâs say I have following monad transformers:
newtype MaybeMonadT m a = MMT (m a)
newtype OuptutMonadT m o a = OMT (m a, o)
And I am trying to use following monads:
type M1 a = MaybeMonadT OutputM a type M2 a = OuptutMonadT Maybe String a
Now, they both wonât quite work in the following example:
foo a b = do output "before" r <- (liftM2(+)) a b when r == Nothing $ output "error" return r
In case of M1, as soon as I get Nothing in r, computation will stop and return without any output gathered.
In case of M2, as soon as I get Nothing in r, computation will stop and return only with output gathered so far. That is
output "error" will never be called.
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

Thanks for response; unfortunately my problem was a bit deeper. Note to self: try to verify all code snippets before posting ;-) The problem is that code on line 4 is useless. If one of the arguments a or b is Nothing, computation will just return Nothing and will not bother to execute lines 4-5: 1 foo a b = do 2 output "before" 3 let r = liftM2 (+) a b 4 when (r == Nothing) $ output "error" 5 return r -- ??? "lift r" I guess it does make perfect sense. It just was not intuitive to me that as soon as I use Maybe monad in my monad transformation I will loose ability to do output *after* error occurred. But, anyway, I think I was able to find a nice solution to that: type M2 a = OuptutMonadT Maybe String a whenError:: M2 a -> M2 a -> M2 a … 1 foo a b = do 2 output "before" 3 let r = liftM2 (+) a b 4 `whenError` $ reportError "error" 5 return r whenError combines two computations so that if first fails it will use second instead. Thanks, Pavel.

On Tue, Nov 30, 2004 at 06:36:46PM +0000, Pavel Zolnikov wrote:
The problem is that code on line 4 is useless. If one of the arguments a or b is Nothing, computation will just return Nothing and will not bother to execute lines 4-5:
1 foo a b = do 2 output "before" 3 let r = liftM2 (+) a b 4 when (r == Nothing) $ output "error" 5 return r -- ??? "lift r"
Obviously you are talking about your version of code (with "r <- liftM2..."), because for Mike's code your statement isn't true. How can "let" abort the computation? Best regards, Tomasz

You are absolutely right! I missed that ‘let’. And it does work, thanks. On the other hand it is still not what I intended, so I decided not to use Monads in this particular case. I wanted to represent values that can fail and that have complete history of errors. So that if I calculate c = (liftM2 (+)) a b and if *both* a and b have failed, c would also fail plus it would have history of errors form a and b (and potentially it’s own). I tried to do this by combining Maybe and Output monads. Now I realize that as long as I am using liftM2 it is not possible to have history of errors from a and b - only form a. And as I moved to custom lift functions I realized I didn’t need monads in the first place. Anyway, thanks for your help, it is very much appreciated. Regards, Pavel. I didn’t know you can have ‘let’ without ‘in’ in do expressions, nice!

On 30 Nov 2004, at 20:55, Pavel Zolnikov wrote:
Now I realize that as long as I am using liftM2 it is not possible to have history of errors from a and b - only form a. And as I moved to custom lift functions I realized I didn’t need monads in the first place.
It's not liftM2 that's the problem, it's rather the strategy of the Maybe monad. The strategy of the maybe monad is very roughly 'abort on error'. As you correctly observe, it doesn't make it at all easy to collect error messages, let alone tree-shaped error graphs. However, your problem *does* have a natural underlying monad, if you care to use it. It's a kind of Error monad, but not the standard one, since you would have to implement your chosen rules for combining errors. I'm not quite sure exactly what you want, probably either a list of errors, or a tree of errors, depending how much structure you're trying to preserve. You may not find the monad approach easier than just coding by hand, though... although if you have a large amount of code, I would expect monads to make it a bit shorter and clearer. Jules

Jules Bean wrote:
However, your problem *does* have a natural underlying monad, if you care to use it.
I may be confused, but I don't think it does. It seems like the OP wants a type like data Perhaps a = Success a | Failure [Error] and semantics like liftM2 (+) (Failure [error1]) (Failure [error2]) === Failure [error1,error2] where liftM2 f a1 a2 = a1 >>= p where p v1 = a2 >>= q where q v2 = return (f v1 v2) I don't see how to define (>>=) such that this will return the appropriate value. If a1 fails you must call p in order to collect additional errors, but there's no appropriate value of v1 that you can pass. But it's easy with a custom lifting function: liftPerhaps2 f (Success x) (Success y) = Success (f x y) liftPerhaps2 f p q = Failure (errors p ++ errors q) where errors (Success _) = [] errors (Failure es) = es -- Ben

Ben Rudiak-Gould
I may be confused, but I don't think it does. It seems like the OP wants a type like
data Perhaps a = Success a | Failure [Error]
When writing a compiler, it makes sense to collect errors as by the writer monad, and not abort anything - producing dummy values instead (except perhaps some fatal errors when it's inconvenient). -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/

On 1 Dec 2004, at 00:37, Marcin 'Qrczak' Kowalczyk wrote:
Ben Rudiak-Gould
writes: I may be confused, but I don't think it does. It seems like the OP wants a type like
data Perhaps a = Success a | Failure [Error]
When writing a compiler, it makes sense to collect errors as by the writer monad, and not abort anything - producing dummy values instead (except perhaps some fatal errors when it's inconvenient).
Or you could use the monad: data Perhaps a = Success a | Failure a [Error] instance Monad Perhaps where Success a >>= k = k a Failure a es >>= k = Failure (value (k a)) (errors (k a) ++ es) where value Success b = b value Failure b _ = b errors Success _ = [] errors Failure _ fs = fs ...such that failures must also produce a value (which could be some approximating 'best guess' based on incorrect input, or some simple default value). This is just a special case of the writer monad, I think. Jules

Jules Bean
When writing a compiler, it makes sense to collect errors as by the writer monad, and not abort anything - producing dummy values instead (except perhaps some fatal errors when it's inconvenient).
Or you could use the monad:
data Perhaps a = Success a | Failure a [Error]
This is just a special case of the writer monad, I think.
It's the same as the writer monad, with redundant encoding of the case when there are no errors. -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/

On 30 Nov 2004, at 23:29, Ben Rudiak-Gould wrote:
Jules Bean wrote:
However, your problem *does* have a natural underlying monad, if you care to use it.
I may be confused, but I don't think it does. It seems like the OP wants a type like
data Perhaps a = Success a | Failure [Error]
[snip]
I don't see how to define (>>=) such that this will return the appropriate value. If a1 fails you must call p in order to collect additional errors, but there's no appropriate value of v1 that you can pass.
Yes, I see I spoke too quickly. Perhaps a = Success a | Failure [Error] is a monad, but the bind (>>=) operation doesn't do what the OP needs, since just like the bind for maybe, it will abort the computation when an error is returned. But you can define combinators in this monad which do what the OP wants (and you did one of them in your message), it's just that haskell's do notation doesn't work, since the do notation is a shorthand for >>=, and >>= is the wrong strategy for this problem. Jules

On Tue, 30 Nov 2004 18:36:46 +0000 (UTC), Pavel Zolnikov
[..] type M2 a = OuptutMonadT Maybe String a whenError:: M2 a -> M2 a -> M2 a …
1 foo a b = do 2 output "before" 3 let r = liftM2 (+) a b 4 `whenError` $ reportError "error" 5 return r
whenError combines two computations so that if first fails it will use second instead.
FYI, this is what the class MonadPlus is meant for. Check out the Monad module in the standard library. /Josef
participants (7)
-
Ben Rudiak-Gould
-
Josef Svenningsson
-
Jules Bean
-
Marcin 'Qrczak' Kowalczyk
-
Mike Gunter
-
Pavel Zolnikov
-
Tomasz Zielonka