Is try_call_finally/3 the same as setup_call_cleanup_each/3?

I don’t understand your point … exceptions, by their very nature, happen at places you’re not prepared to deal with them, and the throw/catch mechanism bubbles them up to something that is prepared to deal with them. (The alternative is to do something like Go’s constant error checking.)

Prolog I/O is interesting in that it doesn’t throw an exception if an attempt is made ot read past end-of-file (it returns end_of_file or [] or similar, depending on the call); but I presume it can still throw an exception if an I/O error happens. So, robust code has to be prepared to deal with exceptions in some manner.

Dealing with an exception isn’t necessarily difficult. In the 1970s, the DMS/100 operating system (for digital telephone switches) was designed for “5 nines” up-time and its standard technique for dealing with exceptions was to log the error and restart the failed process (typically, processes that encountered a “can’t happen” situation took advantage of this by calling commitsuicide, so that they would be reincarnated in a clean state). Systems written in Erlang (again, often used in telephony software) do something similar.

So, I don’t see why the exception of trying to do I/O on a closed stream should be any different from an exception of trying to do I/O on a disk that’s failed. (Although the former is more likely due to a software bug; but bugs are just as unexpected as hardware errors, and happen much more frequently.)

As I recall, Jan W. @jan wasn’t terribly fond of the idea at the time. The ‘try_finally/2’ that you see in the library(tipc/tipc) was his variation on the theme. The operator (~>)/2 in file, “eventually_implies.pl”, is not part of the SWIPL distro. However, there is nothing extraordinary about defining new operators for the purpose of hiding tedium. Caveat Emptor.

Back in the day, predicates ‘setup_call_cleanup/3’ and its variants were in flux and were being considered for ISO standardization. I think that this effort failed. They are not ISO standard predicates, AFAIK.

If you want correct programs, then you need reliable programs. And that means fault-tolerant programs, or at least fail-safe programs.

"... The price of reliability is the pursuit of the utmost simplicity. It is a price which the very rich find most hard to pay." -- C. A. R. Hoare

Yes. You must always be prepared. That is, you must have an exception-catcher of last resort in order to prevent the program from crashing. If you can’t do something, then backtrack and do something simpler, and if you can’t do that then backtrack and do something simpler still, and if you can’t do anything at all, then wait until you can.

IMHO, using exceptions as a means of control flow, is about the same as tossing hand grenades into foxholes. The only thing that you can be be assured of afterwards is a mess. Erlang has the supervisor/worker pattern, whereby if a worker dies, then a replacement can be instantiated by the supervisor. But how much wreckage did the worker leave behind? You don’t know.

Thankfully, we do not have to keep an accounting of who-throws-what like JAVA like does.

In Prolog, we need only to guard against the exceptions that we’re likely to encounter – for the trouble that we can think of. BUT, we also need to be in a position to do something reasonable for the trouble that we cannot think of. Like Erlang, fail fast, fail often, go offline if you must, but never crash. Crashing is not an option. Crashing could cascade into a terrible mess and bring down systems of systems. Or worse, having a system that is only half-crashed. How much wreckage would that leave behind? You don’t know.

  • That draft is dated 2011. It’s interesting to note that among others, both your and my names are on the proposal.
  • Yeah, it’s really cool. I use it everywhere.
  • Good.
  • What?! Who cares if it’s ISO or not.

Me among them. The most interesting question to me is: “What was it like before a breakthrough, and what compelled those who did it to do it in the first place?” Answer: Juxtaposition of these two ideas.

The earliest specimen that I have of the (~>)/2 operator (eventually_implies.pl) is 2011-02-04

According to GIT log, try_finally/2, existed in tipc.pl in February, 2009.

the most portable version that gets a “without interrupts”

 setup_call_cleanup(
        (asserta(X), undo(retract(X))), 
        true,
        true).

There is one more issue than interrupts. Registering the undo/1 can fail due to lack of resources as well. With setup_call_cleanup/3 that is not a problem as the cleanup handler is already on the stacks before we call the setup. The logic of setup_call_cleanup/3 only has to ensure that if setup completes and an interrupt arrives immediately afterwards it must either start the call or immediately run the cleanup.

Note that the setup call itself must ensure that if one of its steps go wrong the side-effects of earlier steps are undone before failing/raising an exception. So, this is wrong:

setup_call_cleanup((open(F1, read, In), open(F2, write, Out)), ...., (close(In,Close(Out)).

Instead, nest two setup_call_cleanup/3 calls