
On 27/02/06, John Meacham
On Mon, Feb 27, 2006 at 10:57:17PM -0500, Cale Gibbard wrote:
Well, this is an issue. Perhaps a version of error which makes the line/column number available to its parameter would help... something along the lines of
type SourcePos = (Integer, Integer) -- possibly a data/newtype with a nicer Show instance errorPos :: (SourcePos -> String) -> a
Yes, this is what jhc's SRCLOC_ANNOTATE addreses, more or less.
This would give all the benefits normally acquired from the expansion of the syntax sugar while allowing you to additionally add any extra messages you'd like. Further, you'd not be required to work in, say the identity monad, in order to get line number messages for failures (though in GHC at least, irrefutable pattern match failures in lambdas and let also get line numbered).
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.
The writer of a library shouldn't decide how (non-buggy) failure should 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.
I'm actually really against the inclusion of fail in the Monad class, so finding a reasonable replacement for any constructive uses it might have had is important to me.
I know you keep saying this, We start with the exact same premises and goals, yet somehow come to the exact opposite conclusion. I have not quite figured out why.
However, a quick survey shows that _every single_ monad defined in the standard and fptools libraries has an interesting non-error 'fail' method other than Identity, whose sole purpose is to turn 'fail's into errors. Separating out a MonadError with 'fail' seems rather odd as every monad will be an instance of it! (including Identity, since turning fails into errors is its main purpose)
(the monads like 'Reader' and 'Writer' are actually just shorthand for ReaderT a Identity, the inner monad determines the failure mode)
John
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. 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. - Cale