Re: [Haskell-cafe] Re: Debugging partial functions by the rules

From: Robert Dockins
It seems to me that every possible use of a partial function has some (possibly imagined) program invariant that prevents it from failing. Otherwise it is downright wrong. 'head', 'fromJust' and friends don't do anything to put that invariant in the program text.
Well, not really. For example, I often write programs with command line arguments, that contain code of the form do ... [a,b] <- getArgs ... Of course the pattern match is partial, but if it fails, then the standard error message is good enough. This applies to "throw away" code, of course, and if I decide to keep the code then I sooner or later extend it to fix the partiality and give a more sensible error message. But it's still an advantage to be ABLE to write the more concise, but cruder version initially. This isn't a trivial point. We know that error handling code is a major part of software cost--it can even dominate the cost of the "correct case" code (by a large factor). Erlang's "program for the correct case" strategy, coupled with good fault tolerance mechanisms, is one reason for its commercial success--the cost of including error handling code *everywhere* is avoided. But this means accepting that code *may* very well fail--the failure is just going to be handled somewhere else. Haskell (or at least GHC) has good exception handling mechanisms too. We should be prepared to use them, and "let it fail" when things go wrong. The savings of doing so are too large to ignore. John

John Hughes wrote:
From: Robert Dockins
It seems to me that every possible use of a partial function has some (possibly imagined) program invariant that prevents it from failing. Otherwise it is downright wrong. 'head', 'fromJust' and friends don't do anything to put that invariant in the program text.
Well, not really. For example, I often write programs with command line arguments, that contain code of the form
do ... [a,b] <- getArgs ...
Of course the pattern match is partial, but if it fails, then the standard error message is good enough.
This applies to "throw away" code, of course, and if I decide to keep the code then I sooner or later extend it to fix the partiality and give a more sensible error message. But it's still an advantage to be ABLE to write the more concise, but cruder version initially.
This isn't a trivial point. We know that error handling code is a major part of software cost--it can even dominate the cost of the "correct case" code (by a large factor). Erlang's "program for the correct case" strategy, coupled with good fault tolerance mechanisms, is one reason for its commercial success--the cost of including error handling code *everywhere* is avoided. But this means accepting that code *may* very well fail--the failure is just going to be handled somewhere else.
Haskell (or at least GHC) has good exception handling mechanisms too. We should be prepared to use them, and "let it fail" when things go wrong. The savings of doing so are too large to ignore.
But note that Erlang will give you a stack trace for unhandled exceptions which Haskell (currently?) doesn't/can't. Also, I remember an Erlang expert (Ulf Wiger?) stating recently that 'catching and re-throwing expections in most cases tends to hide program errors rather than avoid them' (quoted non-verbatim from memory, I can't recall the name of the paper). Lastly, Erlang's "program for the correct case" is not meant for things like input of data from sources outside the program's control, but only applies to not-checking for program internal invariants. I would be very surprised if there were Erlang experts encouraging "throwaway" code such as your example above. Cheers, Ben

On Wednesday 15 November 2006 15:53, John Hughes wrote:
From: Robert Dockins
It seems to me that every possible use of a partial function has some (possibly imagined) program invariant that prevents it from failing. Otherwise it is downright wrong. 'head', 'fromJust' and friends don't do anything to put that invariant in the program text.
Well, not really. For example, I often write programs with command line arguments, that contain code of the form
do ... [a,b] <- getArgs ...
Of course the pattern match is partial, but if it fails, then the standard error message is good enough.
I'd actually put this in a different category than 'partial function' (in what might be regarded as an abuse of termonology). This is failure in a monad, and is something I personally use a lot. Failure in IO just usually happens to have behavior very similar to calling 'error'. I'll often write code in an arbitrary monad just to model partiality via the 'fail' function. Sometimes, as here, I use partial pattern matches to do this implicitly. Why is this better than 'error'? Because it allows the code consumer decide how to deal with problems. You can use runIdentity to convert 'fail' to 'error'. You can run with runErrorT and recover the error message. You can run it in a custom moand that has some other fancy error handling. etc, etc.
This applies to "throw away" code, of course, and if I decide to keep the code then I sooner or later extend it to fix the partiality and give a more sensible error message. But it's still an advantage to be ABLE to write the more concise, but cruder version initially.
I'm not against partial pattern matching. I think it's way better than using partial projection functions.
This isn't a trivial point. We know that error handling code is a major part of software cost--it can even dominate the cost of the "correct case" code (by a large factor). Erlang's "program for the correct case" strategy, coupled with good fault tolerance mechanisms, is one reason for its commercial success--the cost of including error handling code *everywhere* is avoided. But this means accepting that code *may* very well fail--the failure is just going to be handled somewhere else.
Haskell (or at least GHC) has good exception handling mechanisms too. We should be prepared to use them, and "let it fail" when things go wrong. The savings of doing so are too large to ignore.
John
-- Rob Dockins Talk softly and drive a Sherman tank. Laugh hard, it's a long way to the bank. -- TMBG

This isn't a trivial point. We know that error handling code is a major part of software cost--it can even >dominate the cost of the "correct case" code (by a large factor). Erlang's "program for the correct case" >strategy, coupled with good fault tolerance mechanisms, is one reason for its commercial success--the cost of >including error handling code *everywhere* is avoided. But this means accepting that code *may* very well >fail--the failure is just going to be handled somewhere else.
I've been waiting for someone to put in a quick word for Erlang's approach;-) if I recall correctly, the opposite to "programming for the correct case" was to "program defensively" - try to predict and handle all errors where they occur, and the arguments against that style were that: 1 it spreads error handling all over the place, obscuring the correct case, instead of factoring out the handling code into a coherent framework 2 it tries to handle errors where there is nothing to be done about them, leading to potentially dangerous "default values", or catch/rethrow, or even sweaping helpful diagnostics under the carpet. As I've been trying to argue, the Haskell language report forces implementations to handle functions with non-exhaustive left-hand sides defensively: - the left-hand sides are translated into a case on the right-hand side - the case is completed by a default branch calling error since this behaviour is implicit, there is little that the programmer can do about it, and unlike inlined partial matches (such as your getArgs example), this forces the error to be raised where the offending context is no longer available. it is perhaps a small thing, and mostly concerns everyone just once (because once bitten, we try to "untrain" ourselves and our co-workers from using those "easy" functions like head or tail, fromJust, etc.), but perhaps Haskell' should fix this? Instead of forcing implementations to let non-exhaustive functions accept responsibility for arguments they are not equipped to handle, such arguments should be rejected at the call site, with more useful error context.
Haskell (or at least GHC) has good exception handling mechanisms too. We should be prepared to use them, and "let it fail" when things go wrong. The savings of doing so are too large to ignore.
indeed, though lazyness makes these things a little more cumbersome. "let it fail" really means "don't worry about it here", but "have a safety net separate from the main act" - lazyness might keep our failing trapeze artists in mid flight until someone chooses to observe them, possible forcing them to complete their crash after the safety net has been removed.. Claus -- Data.Ransom: API documentation withheld pending initial payment
participants (4)
-
Benjamin Franksen
-
Claus Reinke
-
John Hughes
-
Robert Dockins