partial functions / failure, Maybe and MonadError and good style

Although terse, the subject really says it all. If i've a partial function, like a parser, what is considered good style for a library. The tradeoffs that I can see are that Maybe is a binary operation, while Error can communicate more information in the type of the error case. Is there some way to defer the error handling Monad to the calling context?

On Fri, Dec 22, 2006 at 08:05:08PM -0500, Steve Downey wrote:
Although terse, the subject really says it all. If i've a partial function, like a parser, what is considered good style for a library. The tradeoffs that I can see are that Maybe is a binary operation, while Error can communicate more information in the type of the error case. Is there some way to defer the error handling Monad to the calling context?
Your title answers the question. :) All monads provide a "fail" operation, and any function that uses fail will be able to work with any monad's error handling mechanism. * Maybe - fail msg is Nothing (ignores the message) * Either str - fail msg is Left msg (stores the message) * IO - fail msg is ioError (userError msg) (throws message as exception) For instance, Data.Map.minView is Monad m => Set a -> m (Set a, a); so.. minView :: Set a -> Maybe (Set a, a) -- gives Nothing on empty set minView :: Set a -> [(Set a, a)] -- gives [] on empty set minView :: Set a -> IO (Set a, a) -- throws an ioError on empty set ... (note that the behaivor of lookup et al, which are specific to Maybe, is considered outdated style)

I would not advocate using the fail operation in a monad. It doesn't belong there, and hopefully it will go away some day. :) -- Lennart On Dec 23, 2006, at 02:21 , Stefan O'Rear wrote:
On Fri, Dec 22, 2006 at 08:05:08PM -0500, Steve Downey wrote:
Although terse, the subject really says it all. If i've a partial function, like a parser, what is considered good style for a library. The tradeoffs that I can see are that Maybe is a binary operation, while Error can communicate more information in the type of the error case. Is there some way to defer the error handling Monad to the calling context?
Your title answers the question. :)
All monads provide a "fail" operation, and any function that uses fail will be able to work with any monad's error handling mechanism.
* Maybe - fail msg is Nothing (ignores the message) * Either str - fail msg is Left msg (stores the message) * IO - fail msg is ioError (userError msg) (throws message as exception)
For instance, Data.Map.minView is Monad m => Set a -> m (Set a, a); so..
minView :: Set a -> Maybe (Set a, a) -- gives Nothing on empty set minView :: Set a -> [(Set a, a)] -- gives [] on empty set minView :: Set a -> IO (Set a, a) -- throws an ioError on empty set ...
(note that the behaivor of lookup et al, which are specific to Maybe, is considered outdated style) _______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Sat, Dec 23, 2006 at 02:33:51AM +0100, Lennart Augustsson wrote:
I would not advocate using the fail operation in a monad. It doesn't belong there, and hopefully it will go away some day. :)
On Fri, Dec 22, 2006 at 08:37:05PM -0500, Steve Downey wrote:
OK, but is msg always a string? Admidtedly, in the concrete case I have at hand (follow up posting RSN), that would be fine. I think I've also been looking at the map lookup case, where not only is lookup failure to be expected, it's almost an imposition to say something other than Nothing.
There is a more general form of monads that support failure, Control.Monad.Error.MonadError in the extended standard libraries; throwError noMsg :: (Error e, MonadError e m) => m a throwError . strMsg :: (Error e, MonadError e m) => String -> m a Error is any type that strings can be mapped into (I'm not talking about an injection, I just can't find a better word), such as String and IOError. Unfortunately there does not seem to be a standard instance for Maybe... -- This isn't in the standard library, and thus I conjecture that it -- is seriously flawed in a way I haven't noticed. instance Error () where noMsg = () ; strMsg _ = () instance MonadError () Maybe where throwError _ = Nothing Just x `catchError` f = Just x Nothing `catchError` f = f ()

Actually, that is the version of Error I was looking at.
On 12/22/06, Stefan O'Rear
On Sat, Dec 23, 2006 at 02:33:51AM +0100, Lennart Augustsson wrote:
I would not advocate using the fail operation in a monad. It doesn't belong there, and hopefully it will go away some day. :)
On Fri, Dec 22, 2006 at 08:37:05PM -0500, Steve Downey wrote:
OK, but is msg always a string? Admidtedly, in the concrete case I have at hand (follow up posting RSN), that would be fine. I think I've also been looking at the map lookup case, where not only is lookup failure to be expected, it's almost an imposition to say something other than Nothing.
There is a more general form of monads that support failure, Control.Monad.Error.MonadError in the extended standard libraries;
throwError noMsg :: (Error e, MonadError e m) => m a throwError . strMsg :: (Error e, MonadError e m) => String -> m a
Error is any type that strings can be mapped into (I'm not talking about an injection, I just can't find a better word), such as String and IOError.
Unfortunately there does not seem to be a standard instance for Maybe...
-- This isn't in the standard library, and thus I conjecture that it -- is seriously flawed in a way I haven't noticed.
instance Error () where noMsg = () ; strMsg _ = () instance MonadError () Maybe where throwError _ = Nothing
Just x `catchError` f = Just x Nothing `catchError` f = f ()

OK, but is msg always a string? Admidtedly, in the concrete case I
have at hand (follow up posting RSN), that would be fine.
I think I've also been looking at the map lookup case, where not only
is lookup failure to be expected, it's almost an imposition to say
something other than Nothing.
On 12/22/06, Stefan O'Rear
On Fri, Dec 22, 2006 at 08:05:08PM -0500, Steve Downey wrote:
Although terse, the subject really says it all. If i've a partial function, like a parser, what is considered good style for a library. The tradeoffs that I can see are that Maybe is a binary operation, while Error can communicate more information in the type of the error case. Is there some way to defer the error handling Monad to the calling context?
Your title answers the question. :)
All monads provide a "fail" operation, and any function that uses fail will be able to work with any monad's error handling mechanism.
* Maybe - fail msg is Nothing (ignores the message) * Either str - fail msg is Left msg (stores the message) * IO - fail msg is ioError (userError msg) (throws message as exception)
For instance, Data.Map.minView is Monad m => Set a -> m (Set a, a); so..
minView :: Set a -> Maybe (Set a, a) -- gives Nothing on empty set minView :: Set a -> [(Set a, a)] -- gives [] on empty set minView :: Set a -> IO (Set a, a) -- throws an ioError on empty set ...
(note that the behaivor of lookup et al, which are specific to Maybe, is considered outdated style)

On Fri, Dec 22, 2006 at 08:37:05PM -0500, Steve Downey wrote:
On 12/22/06, Stefan O'Rear
wrote: All monads provide a "fail" operation, and any function that uses fail will be able to work with any monad's error handling mechanism.
* Maybe - fail msg is Nothing (ignores the message) * Either str - fail msg is Left msg (stores the message) * IO - fail msg is ioError (userError msg) (throws message as exception)
OK, but is msg always a string?
You've hit the nail on the head. The fail method is a kludge that works if the error is just a string (in English, of course), but often it isn't.
For instance, Data.Map.minView is Monad m => Set a -> m (Set a, a); so..
minView :: Set a -> Maybe (Set a, a) -- gives Nothing on empty set minView :: Set a -> [(Set a, a)] -- gives [] on empty set minView :: Set a -> IO (Set a, a) -- throws an ioError on empty set ...
(note that the behavior of lookup et al, which are specific to Maybe, is considered outdated style)
I feel a bit queasy about this. With the old Maybe type of minView, it really was a view: the empty case was an element of the data type, not an error. You could pattern match on it, and the empty case would often be perfectly normal. Of course you can still do that, because the old type is a special case that's still available, but the change of perspective gives the wrong impression, I think. It feels wrong to use failure as a normal execution path.

'normal execution path' is a key phrase here. If we're truely thinking
functionally, there is no execution path. At most there are evaluation
strategies.
I suppose this is why Maybe is a monad rather than just another
algebraic data type.
Perhaps I'm thinking of the strategy of 'replacing failures by a list
of sucesses' where an empty list is possible.
This is exactly where I'm stuck. I've an eval1 function of ArithExpr
-> m ArithExpr, where m started out as Maybe. Now only the calling
eval function mentions Maybe. And I was wondering if I could keep
eval1 generic.
OTOH, eval1 should really pass through information from the underlying parser...
Really RSN. When me, my laptop, and my wireless connection all meet at
the same time.
On 12/22/06, Ross Paterson
On Fri, Dec 22, 2006 at 08:37:05PM -0500, Steve Downey wrote:
On 12/22/06, Stefan O'Rear
wrote: All monads provide a "fail" operation, and any function that uses fail will be able to work with any monad's error handling mechanism.
* Maybe - fail msg is Nothing (ignores the message) * Either str - fail msg is Left msg (stores the message) * IO - fail msg is ioError (userError msg) (throws message as exception)
OK, but is msg always a string?
You've hit the nail on the head. The fail method is a kludge that works if the error is just a string (in English, of course), but often it isn't.
For instance, Data.Map.minView is Monad m => Set a -> m (Set a, a); so..
minView :: Set a -> Maybe (Set a, a) -- gives Nothing on empty set minView :: Set a -> [(Set a, a)] -- gives [] on empty set minView :: Set a -> IO (Set a, a) -- throws an ioError on empty set ...
(note that the behavior of lookup et al, which are specific to Maybe, is considered outdated style)
I feel a bit queasy about this. With the old Maybe type of minView, it really was a view: the empty case was an element of the data type, not an error. You could pattern match on it, and the empty case would often be perfectly normal. Of course you can still do that, because the old type is a special case that's still available, but the change of perspective gives the wrong impression, I think. It feels wrong to use failure as a normal execution path.
_______________________________________________ Haskell-Cafe mailing list Haskell-Cafe@haskell.org http://www.haskell.org/mailman/listinfo/haskell-cafe

On Fri, Dec 22, 2006 at 09:44:35PM -0500, Steve Downey wrote:
I suppose this is why Maybe is a monad rather than just another algebraic data type.
Maybe is an algebraic datatype. A monad is simply a structure defined on the datatype, reflecting a normal pattern of use.
This is exactly where I'm stuck. I've an eval1 function of ArithExpr -> m ArithExpr, where m started out as Maybe. Now only the calling eval function mentions Maybe. And I was wondering if I could keep eval1 generic.
That should be easy - anything you can do in Maybe or Either can be done in an arbitrary MonadError. f :: (MonadError e m, Error e) => Maybe a -> m a f Nothing = throwError noMsg f (Just x) = return x g :: (MonadError e m, Error e) => Either String a -> m a g (Left e) = throwError $ strMsg e g (Right x) = return x h :: (MonadError e m) => Either e a -> m a h (Left e) = throwError e h (Right x) = return x
OTOH, eval1 should really pass through information from the underlying parser...
If you have a type of errors, pattern 'h' above should be applicable?

On Saturday 23 December 2006 14:21, Stefan O'Rear wrote:
On Fri, Dec 22, 2006 at 08:05:08PM -0500, Steve Downey wrote:
Although terse, the subject really says it all. If i've a partial function, like a parser, what is considered good style for a library. The tradeoffs that I can see are that Maybe is a binary operation, while Error can communicate more information in the type of the error case. Is there some way to defer the error handling Monad to the calling context?
Your title answers the question. :)
All monads provide a "fail" operation, and any function that uses fail will be able to work with any monad's error handling mechanism.
Though this obviously forces you to encode your library's error information into a String, which is a serious limitation in my book. If your library is in an Error monad it seems straight forward for a consumer to get the fail behaviour, or even provide a wrapper in the library. Going the other way and having to decode Strings into richer error constructs may or may not be straight forward. I recall there has been previous (lengthy) discussion on errors and fail in this list. It might be worth having a trawl to get the different points of view. Daniel
participants (5)
-
Daniel McAllansmith
-
Lennart Augustsson
-
Ross Paterson
-
Stefan O'Rear
-
Steve Downey