When does assert fail? Running out of space?

Hello,

I am trying to make my application robust by handling conceivable errors.

I am expecting to assert many facts – and i am wondering, how can assert fail – or does it fail when prolog runs out of space – can such an error be caught?

thanks,

Dan

Oh, i just noticed this … in the manual (assert/1)

can raise a resource_error(program_space) exception.

1 Like

How does one typically test for running out of space ?

See: Exception handling

See: test_resource_error.pl

or

search GitHub SWI-Prolog swipl-devel for program_space

1 Like

See: SWI-Prolog assert test cases


?- X = f(X).
X = f(X).

?- X = f(X),assert(X).
ERROR: Cannot represent due to `cyclic_term'
ERROR: In:
ERROR:   [11] assert(f(f(...)))
ERROR:    [9] <user>
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.
?- X = f(X,1).
X = f(X, 1).

?- X = f(X,1),assert(X).
ERROR: Cannot represent due to `cyclic_term'
ERROR: In:
ERROR:   [11] assert(f(f(...,1),1))
ERROR:    [9] <user>
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.
?- X = f(X),assert((f(a) :- X)).
ERROR: Cannot represent due to `cyclic_term'
ERROR: In:
ERROR:   [11] assert((f(a):-f(...)))
ERROR:    [9] <user>
ERROR: 
ERROR: Note: some frames are missing due to last-call optimization.
ERROR: Re-run your program in debug mode (:- debug.) to get more detail.

There is one more test in the test cases, but I don’t know how to do it here as a simple one liner.

I’d guess:

...
catch(assert(X),
      error(resource_error(program_space), _ImplementationDefined),
      (format("Oops, I've run out of space~n", []), % stub for plan B when out of space
       fail)  % needs to be called to avoid proceeding with next step.                
     ), 
format("All cool, enough space~n",[]), % no error, so continue with plan A as normal
...

(I’ve edited the above after reading @jan’s very helpful post below.)
(A further edit changing print to format on @jan’s advice.)

Thanks,

But, wouldn’t the above call both prints in case of running out of space …

Catch doesn’t fail upon an exception – or does it ?

I’m not sure. I’ve used the code as above and it seems to work, but I’ve had to figure it out from trial and error.

catch (:Goal, +Catcher, :Recover) sadly seems to be another fundamental part of Prolog lacking simple, illustrative examples.

From Googling, I see you’ve asked for clarification on catch/throw here https://swi-prolog.discourse.group/t/catch-throw-recover-until-its-suceeds/288,and I’d also appreciate a clear answer on how to use catch.

thanks.

I’ll post an example that i got to work …

Now, I am trying something different … to wrap catch/3 into logtalk’s expected reference …

https://logtalk.org/library/expected_1.html

Dan
Dan

I too had a similar problem when first trying to use setup_call_cleanup/3 (other post) but after spending an hour or two looking for examples in the SWI-Prolog source code on GitHub found several revealing examples, which are documented in the other post, and now I am quite comfortable using it.

Be aware that all error terms are of the shape error(Formal, ImplementationDefined). The Formal part is resource_error(program_space). The ImplementationDefined is typically left a variable in SWI-Prolog and bound to a stack trace if the exception is uncaught or caught by catch_with_backtrace/3. Typically, if you want to catch a specific error you use

catch(Goal, error(resource_error(program_space), _), <recover>).

If you want to print, a common pattern is the one below. The Error = error(_,_) prevents trapping special exceptions such as from abort, call_with_time_limit/2, etc.

Error = error(_,_),
catch(Goal, Error, (print_message(error, Error), fail)),
...

In general though, SWI-Prolog deals poorly with really running out of memory (as in C malloc() returning NULL) and in most places reports this as a fatal error and dies. Newer code often throws resource_error(memory), but soon enough you’ll bump into older code that kills the process. program_space is property of a module that is, for example, used by SWISH to avoid SWISH users crashing the server by making it run out of memory using e.g., assert/1.

There is a lot more to say about handling errors. In general I do not like the current situation much. In the XPCE graphics systems there are three types of errors: fatal errors that require termination, programming errors that are (99%) caused by a broken program such as typing errors or existence errors of classes and methods and environment errors that are typically caused by e.g., missing files, files containing invalid data), etc.

As is, for example if you try to open and read a file, there are a lot of different environment errors possible: existence error of the file, permission errors, representation errors for (Unicode) characters and I/O errors (last two not formally described by ISO AFAIK). But, such a piece of code may also be subject to all sorts of programming errors such as instantiation and type errors. In the first class you want to inform the application user as good and human friendly as possible what is wrong. In the second class you want to produce an informative error report that the application user can send to the developers.

Coding the above is hard though. You get something like this:

    Error = error(Formal, Context),
    catch(Goal, Error, true),
    (    var(Formal)
    ->  <ok, go on>
    ;    Formal = existence_error(source_sink, File)
    ->  <tell user File does not exist>
    ;   ...
    -> ....
    ;    <tell user the application is broken and report X to the developers>
    )

Some abstraction can make this a little cleaner, but IMO it stays a mess. I’ve been playing with the idea to consider constraints in the catch/3 ball. That would allow for something like

    environment_error(Error),
    catch(Goal, Error, <tell user about Error>),
    ...

And catch all programming errors on the outside of the entire application. Now we can implement a library that defines a (constraint) taxonomy of errors that need to be handled in a specific way.

4 Likes

I would be interested to know what this is :slight_smile: can you link something we can read (even a research paper would be fine). Googling gives me all kinds of hits in all kinds of fields and I don’t know where to even start reading.

On the topic of “catching programming errors”, yes, there is a lot that can be improved in many directions. I am sure that all of use regularly use many ad-hoc methods and procedures for dealing with this (in addition to the what is already available, like compiler errors and warning, linting, debugging and so on). I’d expect that this is an area of active research: can anyone point to good papers on the subject? (Again, I am too far from this kind of research so I am not sure where to start looking or what to look for.)

How about an Expected approach like in Logtalk, and found in functional languages – and i think more recently, even in javascript.

Could that be a useful basis for more clean error handling – to actually move away from catch/3 for most cases and only leave it for small selected cases (which ones?).

Dan

Again, to offer a completely different angle to this (so sorry if it sounds irrelevant). Also, please take everything I say as just an uneducated person rambling, it is definitely not meant as a critique.

In computer science and software engineering and programming, there seems to be an attitude of "there is the one way to do it right (and the corollary seems to be, “any other approach is hence wrong”). Of course I am exaggerating but this is just to make a point.

In experimental science, people have long ago accepted that nothing can be “known”, just observed, and that no matter how well-designed your experiment is, there are infinitely many ways to get it wrong. So one pragmatic solution is to design several experiments to test the same hypothesis study the same phenomenon. It is important that the experiments approach the problem from an orthogonal direction: use a different assay, use a different model (animal, cell culture, different cell line and so on), use a different point of attack (don’t knock-down the protein, overexpress the inhibitor instead), and so on, and so on.

The underlying idea is to allow yourself to make mistakes in your thinking, but eventually catch them by thinking about it in a different mindset.

I admit that I am not sure how this attitude translates to programming. Of course we have type systems, compilers, linters, unit tests, and so on, but usually, all of this tests the same hypothesis, it seems to me.

This is why I asked for pointers to current research on the topic.

1 Like

As we are at details, the I/O predicates of Prolog are pretty confusing :frowning: To name a few typical oddities and misconceptions:

  • write/1 is a meaningless predicate. It writes a term without quotes, so typically neither users nor Prolog can read the result. Use format/1-3 to write to the user (with the disadvantage that it is not part of the ISO standard, but many systems provide it).
  • writeq/1, or ISO write_term/2 with the option quoted(true) is a little more useful as at least Prolog can read the result provided the same operators are effective. If operators cannot be guaranteed, use write_canonical/1, but be aware that the ISO version is broken. ISO demands lists to be written as '.'(Head,Tail). If you nest this, you get '.'(1, '.'(2, '.'(3, ....))) which implies you can only reduce the input to a list when you arrive at the closing )-s as each term could be a term with ‘.’ and more (or less) than 2 arguments. This wastes a lot of resources when reading long lists.
  • print/1 or ISO write_term/2 using `portray(true)’ is for printing debug messages. Here, portray/1 rules can affect the output so you do not know what is actually sent to the output. The portray rules should ensure that complex and large terms are written in a way that is useful for the developer, not the end user. Print/1 is rarely used directly, but through ~p in format/1-3 or more commonly in debug/3. Typically, libraries should not print anything, but if they do they better use print_message/2 Anne Ogborn wrote a tutorial on this.
  • read/1 and family are more widely known not to do what the novice user expects and has less pitfalls.

For short, it would have been better if read/1, write/1 and print/1 has not existed. Having write_term/2 and read_term/2 is less confusing. Next, it would have been nice if ISO had defined predicates for application I/O such as format/1-3 and SWI’s read_line_to_* family.

5 Likes

I look at it this way, if you didn’t ask these questions which I too also have, then I would have to write the question and respond to the replies. When you do it I can just sit back and read the post as they come along.

So please keep asking these questions when you are perplexed, because I am too. :yum:

Eric …

What does it take to minimize perplexity for the professionally aspiring Prolog programmer.

For example, i really loved the reading, the book about domain modeling and F# —https://fsharpforfunandprofit.com/books/

Dan

I’ve re-edited to change print("…") to format("…", []).

A reason I like using print for debugging (or format("~p~n", []) rather than format"~w~n",[]) is the output reveals details such if a term is a string or single quoted atom, but in this case it makes no difference.

I don’t know. The Logtalk page doesn’t give examples. Do you have pointers?

What we are after is some way of error handling that is as unobstructive as possible and allows informative messages both talking to the application user and developer.

Typically you should not have to do anything to satisfy the developer. Uncaught exceptions should suffice here. The commonly used approach is to print a stack trace, which is what SWI-Prolog does. This is slightly problematic as last-call-optimization typically removes many frames and Prolog recursion can sometimes be pretty deep. I think it could make sense to have a mode that disables last call optimization for non-recursive calls. One problem are programs using mutual recursion though. This is less common that direct recursion, but not that uncommon that we can ignore it.

If we are not talking about an exception that should terminate the program and must be addressed by the developer things get harder. First, as you want to continue the program, you must make sure to reclaim side effects. setup_call_cleanup/3 is quite ok for that, typically leading to constructs as below. This, IMO, is not too obstructive. It doesn’t catch any errors, allowing the system to distinguish between uncaught and caught errors. This distinction is helpful for the system to support debugging as an uncaught exception signals something the developer did not foresee and thus the system must help the developer analyzing the cause for this exception.

    setup_call_cleanup(
         open(File, read, In),
         process(In),
         close(In)).

But, now we are in the situation that we want to do something with. e.g., a file provided by the user and something going wrong (e.g., tell the user the file is has no read access and provide a try again option to the user). Ideally we would like something that branches control between all ok and error X related to File occurred and not affect anything else using the exception mechanism such as programmer errors, timeouts, etc. As is, ISO catch/3 cannot do that elegantly AFAIK. I’d be glad to stand correct though :slight_smile:

1 Like