[Haskell-cafe] haskell programming guidelines
cgibbard at gmail.com
Tue Feb 28 01:09:03 EST 2006
On 27/02/06, John Meacham <john at repetae.net> wrote:
> 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
> > 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)
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.
More information about the Haskell-Cafe