
On Tue, Feb 28, 2006 at 01:09:03AM -0500, Cale Gibbard wrote:
Well, the benefit of the Identity monad is so that the user of a routine can choose to recover gracefully by using a different monad, you only use the Identity monad when you are making a choice to bottom out on errors. using 'error' directly is not an option in said cases because it would take away the ability of the user of a routine to catch errors properly. error should only be used for reporting bugs that should never happen, not user visible failure.
I'd argue that it would be better for the user to simply catch the value returned which indicates error explicitly, and throw the error themselves. This indicates that they have put thought into the fact that the function may fail.
so does using runIdentity, that is the point of it. You are saying I want failure to bottom out, just like using it as a 'Maybe' means you only care about whether it has a result or using it as a 'Either' means you want the result string or using it as a WriterT Foo IO means you want to possibly collect some results and have fail throw an IO exception. I consider it bad style to spend code on cases you never expect to happen, if it takes too much work to write code that fails properly on bugs, people arn't (and definitly should not have to) do the extra work, they will just write code that fails poorly. Monadic failure is absolutely great for writing robust, concise, code.
be handled, the user of it should.
Right, which is why minimal types for expressing the failure should be used, and the user should convert from those types to whatever larger environment they have in mind. If your function is simply partial, use Maybe, if you want to report error strings, use Either String. These types easily lift into any monad which support similar functionality. It also gives the users of your library more information about the exact way in which your functions may fail, just by looking at the type signatures, and gets them thinking about handling that failure. An arbitrary monad m doesn't indicate anything about the failure modes present.
ack! The user of a library is who should get to choose how to deal with the error case, not the library writer. I'd hate to give up such very common idioms as -- collect error messages from all failing parsers [ err | Left err <- map parse xs] -- look up a string transformed by a map in another map, failing if it -- is not in said other map. runIdentity $ Map.lookup (concatMap (`Map.lookup` map) xs) smap but the real power is when you combine monadic failure with combinators and monad transformers -- imagine some complicated function f x xs = runWriterT $ mapM (\x -> foofunc x >>= tell) xs the great thing about this is it is transparent to failure! so you can build arbitrarily complicated transformers while still letting the user of 'f' decide what to do with failure. this is a great feature, if foofunc returned a data type, the writer of 'f' would be forced to deal with failure, and might (likely will) do something silly like call 'error'. I really don't like it when things fail via 'error'. monadic failure means they don't have to. not only can they let the user decide how failure should be handled, but Monads provide exactly the compositional tools needed to combine code in a such a way that preserves that property. imagine if Map.lookup returned Maybe Int, but writeInt returned (Either String Foo). now suddenly you couldn't do
Map.lookup x map >>= writeInt
By prematurely deciding on an algebraic type, you seriously limit the usability of your code. you say "If your function is simply partial, use Maybe, if you want to report error strings, use Either String." which is exactly precicely what monadic failure lets you do. use the routine in the way that makes sense. but more importantly it lets you write monadic combinators that preserve said property.
Well, that means that Reader, Writer and State, and any monad based upon them or their transformers does not have a meaningful fail. IO also does not have an interesting fail. It also means that all custom monads based on state transformers, say, don't have interesting fails. This is a very large chunk of the monads which people use in everyday code! The List monad and Maybe monad have nice fails, and that's why they should be in MonadZero.
IO definitly has an interesting fail, it throws a catchable IO exception. (note, this is not the same as imprecise exceptions) Reader,Writer, and State are stacked on top of Identity, which has error as fail on purpose. if you don't like that you have the freedom to either stack the transformer version on to another monad. Or there are various transformers that give you an interesting 'fail' if you want it. When you use Identity, you are saying 'error' is what you want. but in any case, you just stated the power of monadic fail right there. "monads based on Reader, Writer, State won't have an interesting fail" but you seem to miss the converse "monads based on ones with interesting fails will have an interesting fail" but who determines what monad code runs in? the _user_ of the code. not the code itself. if you want to handle failure, just use it in a monad that has failure. it is completly up to the user of a routine how to deal with failure and that is the great power of monadic failure and typeclasses.
I disagree that Identity, Reader, Writer, or State should be an instance of MonadError or MonadZero. They should simply not be used for that purpose. I'd like a monad hierarchy where if there is an instance of a class for a monad, then none of the methods of that class are identically bottom. It seems disingenuous to me to say that some type constructor implements certain functionality, and then implement it in a way which crashes the program. If you need failure in your monad, add it explicitly via a transformer, and if you use failure, you should express that via a class. Types and classes should be meaningful and informative about this sort of thing.
I really don't want programs to bottom out, which is why I like monadic failure, it lets me write code that does not do so and use other peoples code in such a way that it doesn't. bottom is bad! we should avoid it, not encourage it! Monadic failure lets us avoid it in a very nice, clean way. not having it would encourage people to write code that bottoms out on failure with no good recovery path. John -- John Meacham - ⑆repetae.net⑆john⑈