[erlang-questions] Debug support on for guards?

Richard O'Keefe ok@REDACTED
Mon May 3 23:55:41 CEST 2010

On May 3, 2010, at 11:53 PM, Henning Diedrich wrote:

> Thank you for the advice Richard!
> The function is to be called often and directly on non-tested data  
> coming in over the network.
> So at first sight, splitting up seems to bloat things instead of  
> making them clearer in this case. There is no moment in time, long  
> up front, when the diagnosis should be done. I'll give splitting up  
> a try to learn how that looks.

Funny, I thought the style I proposed was all about *removing* bloat.

One of the reasons I got interested in literate programming, back when
that was a new thing, was the fact that in typical imperative code,
the normal case could easily get lost in code dedicated to checking
arguments and reporting errors, and with literate programming I could
at least reduce that to
	<<check arguments>>
	if (ok) {
	    <<do the real work>>
	} else {
	    <<report some sort of error>>
and then <<do the real work>> could be uncluttered.

>> Why?  What is the programmer supposed to do about it?
>> More to the point, what is the *program* supposed to do about it?
> 1) Discard the data he/it is trying to parse and tell the data  
> source that it was no good, and why.

But to what level of detail.
Recalling that the data is supposed to have the form
{M,S,U} where M,S,U are all non-negative integers,
"why" a term T might be no good could be

	- T is not a tuple
	- T is a tuple but not of arity 3
	- T is a tuple of arity >= 1
	  whose first element is not a number
	- T is a tuple of arity >= 1
	  whose first element is not an integer
	- T is a tuple of arity >= 1
	  oh heck there are just TOO many possibilities.

And the whole thing is ambiguous.
Take {-1.2,fred,999999999999999999999}.
Is it wrong
	- because the first argument ISN'T AN INTEGER
	- because the first argument IS NEGATIVE
	- because the second argument IS AN ATOM
	- because the third argument IS OUT OF RANGE
(I don't think the code we've seen so far bothered to check that
the arguments were within reasonable bounds, but it should have.)

I've been through this once, when I designed the exception handling
facility in Quintus Prolog and then had to go through all the source
code ensuring that every single operation available to users
reported reasonable errors.  I'm going through it again in my
Smalltalk system, gradually replacing
	"self error: 'I do not like thee Dr Fell'"
	(NastyLecturerError subject: drFell critic: self) signal
and the answer is that as soon as there is more than one value to
inspect you *can't* report every possible error in a perfect way.
Take something like
	anArray copyFrom: firstIndex to: lastIndex
which does what it looks like (inclusive bounds).
If lastIndex - firstIndex < -1, which one of them is at fault?
Any guess you make might be wrong.

I therefore respectfully suggest that any attempt to provide
fine-grained diagnosis of "why" is misguided.  To start with,
if someone sets up a data source that is supposed to be sending
you well formed time stamps, THEY MUST KNOW what a well formed
time stamp is supposed to look like.  You *can* tell them
"<this> is not a well formed timestamp".  And that's *all* you
can or should tell them.  THEY have all the information they
need to make a diagnosis in a way that makes sense for their
situation.  You do NOT.

So here's what you should be doing.

You have a protocol between you and your data source.
Typically, you will have {Action,Operand1,...,Operandn}.
Define types for the operands (in the UBF sense, which
includes ranges). If an operand does not conform to its
type, report that fact, AND NO MORE DETAIL THAN THAT.

Better still, use UBF, and let UBF do the checking.

You *can't* put the reporting in the function that
decodes a timestamp, for the simple reason that it doesn't
know who to report the error *to*, or how.  The function
that decodes a timestamp has that job and no other.
It could possibly do

	decode({M,S,U}) when ... -> {ok,...};
	decode(T) -> {error,invalid_timestamp,T}.

But no more.

> 2) If the data was self-generated, e.g. for testing, the programmer  
> to use this function may make assumptions that are wrong and the  
> programmer to program this function (me) wants a way to inform him  
> in case he runs into trouble.

WHY might the programmer make wrong assumptions?
Is there something desperately wrong with your documentation?

What is it about your situation that lets someone know "this
operation needs a timestamp" but NOT know what a timestamp
is supposed to look like?  If they don't know what a timestamp
is supposed to look like, how are they supposed to make sense
of your detailed diagnostic.  Above all, HOW ON EARTH are they
supposed to make sense of a diagnostic that talks about which
test in some guard failed, when they should never even see the
guard in question?

If you are testing, why can't you use UBF or a similar scheme of
your own devising?

> By providing more information than function_clause. Especially if I  
> can imagine what the wrong assumption may be.

My absolutely uniform experience has been that when people imagine what
the wrong assumption may be, they are WRONG.  It is worse than useless
to make such guesses.  Don't ever waste anyone's time or bandwidth on
imagined causes of errors, just report the errors themselves simply and

You should not be reporting function_clause back to your data source in
the first place.  You should be reporting {type_error,timestamp,T} or
something like that which is framed in terms of your PROTOCOL between
your program and the data source, which neither reveals nor depends on
ANY internal aspect of your program whatever.

It may be that there is more about your program that you could tell us.

More information about the erlang-questions mailing list