Errors considered harmful

So DDL is something akin to program modification, e.g., assert, consult, etc. This implies that any queries I ran before may no longer valid, i.e., they may not give the same answer again. Is that right?

It’s probably not quite like that, because the same principle would apply to retract. I don’t really understand the subtleties of assert, retract and friends because I try to avoid them, so far successfully, as a matter of principle.

But most of my Prolog code is deterministic and a failure is an error.
For the small part of my code that is backtrack-y or database-y, it’s lovely to have failure; but I don’t want that making my other code harder to debug. I want open/4 to throw an error and not quietly fail – and if I want a version of open/4 that fails, it’s easy enough to write (using the existing open/4 and catch/3).
I also want a Hindley-Milner style type checker (which I’m thinking about writing) and, of course, a pony.

Also, the rdet pack.

But these techniques aren’t quite as simple as you say (they often require adding cuts and sometimes wrapping a predicate in a validation predicate); and the debugger can be clumsy when you’re dealing with large complex structures.

Maybe the mode declarations should become part of the language, allowing the compiler to add validation code. However, a better way of describing types is needed.

1 Like

True enough. At the same time, it seems like a good idea to prevent ill-formed lists from being typed in as literals; that’s a parse/compile time check. And I think there’s a case to be made that binding a tail variable (the one after the |) to a non-list is a violation of the list type (there, I said it). It should at least be detected and it’s a secondary question as to whether its an error or failure; it certainly shouldn’t succeed.

It seems to me Prolog dropped the ball on this one. It provides syntax support for lists. If the contract is for proper lists, and I assume that’s the case because I’ve never seen any example code that deals with improperly terminated lists, then it appears that Prolog fails to enforce their proper structure.

I actually don’t mind this being an error, if it’s ever detected. Programmers should be reminded that they constructed an illegal list, nothing to do with logic, and the code should be fixed. Static instances (literals) should be flat out compiler errors IMO.

BNR Prolog actually had variadic compound terms and a list was just one of those, not a cons nested structure. Tail variables could be unified with sequences ending with another tail variable, and there was support right down at the WAM opcode level to maintain proper structure.

Is this the same as CDR-coding?

So maybe that’s where our styles differ. I tend to decompose a predicate into to multiple individual cases and use failure to select which one. Now I consider that that to be heavily non-deterministic but it’s usually relatively shallow.

Now I don’t mind using heavy duty catch mechanisms for errors like opening files and transactions. It’s just when you get type, existence, and domain errors from things like arg/3, global variable access and arithmetic that it starts to irritate me. The additional overhead rivals, and may exceed, the cost of the operation itself. Whereas if they would just fail to my next clause, the result is generally is cleaner and more efficient code (for the style I prefer).

I think they are if you’re already dealing with failure as a fundamental piece of your programming style. Perhaps not so much if the code is “heavily deterministic and failure is an error”. If that was my primary mode/style I’d probably use Python or JavaScript.

If you’re saying that you use clause failures as a kind of if-then-else (or as a selection based on functor-arity), then that’s what I do a lot of – but the overall selection has to succeed. (In some cases, there can be a bit of backtracking, with a predicate call on the right of the :- providing an additional test - but that’s again just part of the if-then-else structure.

In other words, I think that I can rewrite most of my predicates with the new => (Picat) style.

I occasionally use something like convlist or include that depend on predicate success/failure; but the majority of my code could be fairly straightforwardly transformed into Haskell or similar.

As an aside: I’ve dabbled with constraints – they’re lovely when they work, but have their own set of problems for debugging.

1 Like

Not really sure how close the fit is.

In addition to term, BNRP had a low level construct sequence-of-term which was always terminated by a “void”. Tail variables, which were distinct from variables, could only be unified with sequences. Lists and compound terms were built from sequences. This stuff was always a bit beyond my pay grade but here’s a link which might provide a better explanation:

Apologies for imprecise language. I was thinking the exact query repeated would not produce the same result (loss of idempotency).

OK, then perhaps not so different. These are the errors that I’m largely concerned about, i.e., the ones that control execution within a single predicate. And I would argue that this is also the case where converting a failure to en exception is very simple; not that I would choose to do this very often for exactly the reasons being discussed.

As an example, suppose I wanted to implement a compare predicate to be used in predsort that permitted terms in the list to be sorted to be arithmetic expressions with the order to be determined by the value of those expressions.

compare_eval(<,X,Y)  :- X<Y, !.
compare_eval(=,X,Y)  :- X=:=Y, !.
compare_eval(>,X,Y)  :- X>Y, !.
compare_eval(Op,X,Y) :- compare(Op,X,Y).

So this won’t work well because of all the possible ways that arithmetic evaluation can generate errors (type errors, evaluable errors, instantiation errors, …). It’s almost impossible to guard against all of them without effectively writing your own eval, so pretty expensive. But catch is also pretty expensive, particularly the when a handler is invoked.

Without debating the merits of this particular example (there probably aren’t many), this is the sort of thing I’d like to do in a simple and obvious way.

I have a safe_is that just fails if the is/2 errors. I haven’t noticed it coming up in the profile as a time suck, so I think the catch is fine.

Well it depends on how much arithmetic you do. When I initially implemented interval arithmetic before the IEEE support arrived, I had to catch every evaluation to guard for overflows. Then I had to catch again to ensure outward rounding of intervals didn’t overflow (almost impossible case). On top of that, I couldn’t really take advantage of compiled arithmetic that comes with optimization. I can’t remember the exact numbers, but having IEEE support greatly simplified the code and improved the performance by a factor of at least 3, possibly more (there were a lot of other optimizations that were done in parallel).

Even now, based on my profiling results, many CLP(R) problems I’ve looked at spend a third or more of the total execution time doing this kind of stuff during constraint propagation.

And perhaps it’s just me, but I find catch somewhat ugly and difficult to understand at first glance. Yes the operation itself is fine, but exactly which errors are being caught and what is the handler actually doing? fail is fairly straight forward, but that’s really what I wanted the called predicate to do in the first place.

Not to belabour the point:

?- N=1, M=2, time((between(1,1000000,_),S is M+N,fail)).
% 2,000,001 inferences, 0.217 CPU in 0.217 seconds (100% CPU, 9219611 Lips)

?- N=1, M=2, time((between(1,1000000,_),catch(S is M+N,E,true),fail)).
% 3,000,001 inferences, 0.363 CPU in 0.364 seconds (100% CPU, 8268361 Lips)

?- N=1, M=two, time((between(1,1000000,_),catch(S is M+N,E,true),fail)).
% 14,000,001 inferences, 7.320 CPU in 8.074 seconds (91% CPU, 1912446 Lips)

That’s significant in my world; not to mention that catch currently doesn’t allow user defined arithmetic functions.

That requires somebody who knows something about how the Prolog catch/throw mechanism is implemented, but two points:

  • this is on a low end 2009 Mac Pro so relatively ancient hardware.
  • we’ve already tripped over the with_mutex case where MacOS performance is several times slower than Jan’s Ubuntu system. So there’s something going on at the OS kernel level that’s decidedly different. Whether that applies to this case, I have no idea. I do note the CPU utilization is lower than I would expect for the handler case.

Only a factor of 2 :slight_smile:

Definitely not a warm/cold run issue, repeat results are consistent.

Also note the ratio between the fast case (no error) and the slow case (error):
No error: 0.140/0.217 = 1.55.
Error: 8.074/2.666 = 3.03.

All things being equal, that would suggest there’s about a 50% difference due to hardware. All the rest is going somewhere else (?).

Also look at the inference count difference. Counting between as one inference, there’s over 10 times as many inferences when you have to call the handler. Actual Lips differs by a factor of 5.

In any case, the essential point is using catch doesn’t come cheap.

How about something like I proposed earlier:

So reusing the debug_on_error flag:

set_prolog_flag(debug_on_error,false([type_error, domain_error]).

would change any such errors to failures. This is a global environment flag so I assume this would be done at a global level, but perhaps it could be confined to a module. That would be even nicer, IMO.

According to the manual entry for prolog_exception_hook/4 :

This hook predicate, if defined in the module user , is between raising an exception and handling it. It is intended to allow a program adding additional context to an exception to simplify diagnosing the problem. ExceptionIn is the exception term as raised by throw/1 or one of the built-in predicates. The output argument ExceptionOut describes the exception that is actually raised.

The hook is run in ‘nodebug’ mode. If it succeeds, ExceptionOut is considered the current exception. If it fails, ExceptionIn is used for further processing.

I take this to mean I can add information to the exception but I can’t use it to turn an exception into a failure. That’s my problem exactly.

It is the case that some people claim (notably) type and domain errors to be a type of logical failure. I don’t have that much opinion on that matter. From a practical point of view this is hard to realize. built-ins tend to call PL_type_error() or PL_domain_error() and return failure, so that should not be a big issue. User and library code may call throw/1, which gets already less clean to map to a failure (but doable). The biggest problem is confining this. Using a flag we could confine this to a thread. Modules would be more interesting, but errors in built-ins are not related to a module and errors in user/library code are related to the wrong module.

All in all, I think the most practical solution is to define goal expansion for the predicates that you want to fail silently. Built-in support seems a little too hacky to pollute the core system and I don’t sense a lot of support for this. The main use case I get from your examples is a rather uncontrolled form of overloading of operations. That may be perfectly fine for some tasks. I think some DSL is the proper approach to this. The DSL can deal with the necessary error handling and/or rewrite to avoid the errors. Flags that change global behavior are a proven recipe for problems :frowning:

I would agree with those statements.

In my most common use of Prolog as a modeling language having portions of the code working as predicates that return either true or false and don’t trigger errors is desired. I would rather get a false answer then have to add lots of code to catch the error so the model can keep running. When I see a false result when I expect a true result, it is often the case that I need to add more than just simple logic to my model.

For example of one model I am working on, there is an undocumented hidden variable which was causing many of my queries to fail at times. When the variable values was matching how the code worked the result was correct, when the variable value was not how the code worked the result was false. In working with the real world model and collecting data I found how the hidden variable works and once incorporated into the model created with Prolog, the results are not spot on.

Built-ins are my primary concern but perhaps I’m misunderstanding. In pl-prims.c there are 45 calls to PL_error, not including another 5 calls to PL_type_error, 3 to PL_domain error, and 5 more to PL_representation error. Do these not generate exceptions rather than failures? I don’t know how many of these would reasonably mean logical failure, but I suspect there’s quite a few.

Libraries and user code are an issue, but one I think, as a programmer, have some control over. On the other hand I’m stuck with the built-ins, they can’t be redefined. Furthermore, I don’t have the same performance concerns calling a library predicate vs. calling a builtin.

Agreed, but it would be nice to have something to confine. If the default is the status quo I don’t see the risk. If step one was (optionally) no confinement, what exactly would break? Since most programs don’t normally(?) depend on these kinds of low level exceptions from builtins for proper execution, that leaves those with catches of the types of errors I’m interested in. Of those, the ones that just fail are fine. The remaining are those whose proper operation depends on handler code. Obviously those programs wouldn’t change the default without careful consideration.

Bottom line: it’s not obvious to me that a no confinement would be that bad. I barely understand enough about the module system to use it, let alone whether it can be used to contain this kind of semantics.

And this is a possible route I’m willing to explore. But I think it still has a few issues. Just considering the built-ins, there are almost 60 potential exceptions for consideration. It would be extremely helpful if these were documented in the predicate semantics. And this should be extended to library predicates as well, especially if the exceptions are thrown transitively from built-ins. (So document at least; Java, and others, think this important enough to be part of the method/function syntax.)

The second issue is that in majority of existing cases, the guards are already in place due to the usual necessity of avoiding exceptions in running code. So, for working code at least, the checks would actually be done 3 times: in the original code, in the expanded code, and in the builtin to detect the conditions to throw the exception.

Thirdly, and perhaps my failing, I don’t understand the containment semantics of goal_expansion.

How so? There seem to be a limited number of PL_error-like functions. Check a flag, if true continue with existing code, else fail. (Probably a gross over-simplification, I know.)

I don’t relate to this description at all. If I inserted a guard to avoid calling a builtin that might generate an exception, that’s just (premature) failure. I just want to avoid the necessity of the guard. That’s a pretty typical use case isn’t it?

Only with a fair amount of work and the result still has a containment issue (does it not?) and incurs some level of overhead (probably acceptable in most cases, but irksome).

Any examples of those that cause problems? I can think of flags that do this, but I’m not aware of any specific problems. And this particular use seems fairly limited to me.

I freely admit I don’t understand all the ramifications of what I’m proposing so here’s another idea that could be used to implement my objective, albeit at some cost. Permit user:exception to be a handler of last resort for any exception. That would permit the addition of a global handler to optionally turn any exception into a failure under my control.

Aside: If that passes muster, I would also like the option of succeeding (Action=succeed) like a normal catch handler so at some point I could turn an instantiation error into a constraint. And yes, that’s not Prolog, but it is logic programming.

As I understand this, you’re agreeing with me. Am I wrong?

Is this what you meant to say? If “not spot on”, exactly what was the issue?