When does assert fail? Running out of space?

Yes. It went away from the original problem for dealing with the specific errors from assert/1. It is relevant though. assert/1 for example may raise resource errors as well as type, representation and instantiation errors. These typically need to be handled differently. Subsequently the discussion went to the expect API, which IMO deals with where and how errors are handled, not about how to handle the different errors. I added the intercept/3, delimited continuations and a bit more as alternatives to handle exceptional cases depending on the context. The approach is a bit different. Where (simplified) the expect API passes unexpected events as data terms, and leaves the processing of these terms to later stages, the intercept/3 approach tells the lower level how to behave on certain unexpected events (although it can also be used for normal events).

I do not (yet) understand the railway-oriented programming concept well enough to see how this connects.

I do think there are two issues in this discussion:

  • How to handle different errors elegantly. Ideally that should also ensure that errors you do not want to handle can be recognised as such by the system such that it can immediately start helping the developer to debug the issue.
  • (Decide) where to handle unexpected events (catch, expect, intercept, etc.)

Note that also the expect interface should distinguish such cases. In @pmoura’s blog entry, a missing father/mother can also be caused by a resource exception. That is most likely not what you want. I know, you can work around that using the different expected constructors … It occurs to me that the API deal well with passing unexpected events along using a stream. Safely distinguishing expected, unexpected and really unexpected (e.g., resource errors, undefined predicates, …) is a different matter that should probably be addressed in a different library.

I agree. :smile:

I tried to split this out into a separate topic but could not find a clear break.

I am working on a response to hopefully bridge the ideas from F# to Prolog, but don’t if I can successfully do it and how long it will take. :smiley:

1 Like

take the simplest case possible and describe it in both ways – then add a pinch of non determinism to it :slight_smile:

This is a critical misunderstanding of the expected monad and we should not let it linger. The idea of this monad is that either we have an expected value or data on why an expected value is missing. If we don’t have an expected value, there’s no rationale for a two part term. To be blunt, writing “both the missing value and the exception” makes no sense.

My problem is how to I translate an algebraic data type into Prolog. I visited this idea in these post (A, B), but never made it past the point of getting it off the back burner.

Right now the cleanest way I see to put this into working Prolog code is to add state to the predicates with the state containing a compound term for the result, one part for the valid (happy) path and one part for the exception/error (unhappy) path.

I think, perhaps this can be done by collecting and nesting Expected references in logtalk - but, one needs to also add the operators – somehow.

That’s not clean. If you use the same compound term for both the happy path part and the something went wrong part, then, for any given case, one of the parts will be unbound, which means that you always have to look inside the compound term and test. Carrying around unbound arguments for the sake of representing the absence of something is not good practice.

You are probably right, when I quoted the statement I was focused on the “two-argument term” and not so much the “the missing value and the exception”.

In F# the return value with railway-oriented programming is a algebraic data type that is the union of the valid type and another type to represent an error or some other unexpected result. The simplest example in another language would be the Haskell maybe monad but I don’t know Haskell so can’t attest that it is exactly the same.

Yes, that sounds right, and why I note that I don’t know how to create an algebraic data type in Prolog. That is also why I am hesitant to post code trying to explain such because it might be picked up and propagated which would be even worse.

Thanks for the reply. :smiley:

Optionals (aka maybe monad) and expecteds are two different monads serving different scenarios. E.g. in Logtalk you have both optionals and expecteds libraries.

The two blog posts that Daniel already cited should help in understanding the differences:

Optionals: Handling optional data - Logtalk
Expecteds: Handling missing data - Logtalk

OK, but I am trying to bridge the concept of F# railway-oriented programming to Prolog, I was not planning on using concepts from Logtalk since I have not used Logtalk.

I know Dan is using Logtalk for his code but also likes railway-oriented programming and from what I gather would like to cleanly split the code into the happy path (code without exception handling) and unhappy path (deals with the error/exceptions) in the same/similar way as done with railway-oriented programming.

Easy to fix :stuck_out_tongue:

Note that the cascade example used in the Handling missing data blog post is an example of railway-oriented programming.

1 Like

P.S. Added non-deterministic constructors for both the expecteds and optionals libraries. From past experience, both in personal work and contract work, I expect these non-deterministic constructors to be used sparingly compared with the deterministic constructors. Still, it will avoid users writing their own versions from the basic constructors if they required them.

Great.

Would be great if you could also extend one of the blog posts to include an example of their use …

Dan

I still have ideas and things I want to add to this topic but will have next to no screen time today so please don’t consider my absence as agreeing or being done with working on this.

1 Like

I would not say hard, but I would say verbose. Using the ideas from Railway Oriented Programming and an implementation of the expected monad (such as the one in the Logtalk library), we can defined the sequence of steps that we want without using a single conditional and then handle all possible errors with a single handler. As a (bit silly) example, assume that we want to parse the contents of a text file as a URL and extract the URL attributes by doing the following steps:

  1. Open file.
  2. Read file to codes.
  3. Convert the codes into an atom.
  4. Parse the atom as a URL.

The file may not exist. Reading the file may fail. The file contents may not be a valid URL… Let’s just assume/pretend that different exceptions may be throw at most steps. As the predicates that will be called don’t take or return expected terms we would need to construct and map them as we go from step to step. Additionally, when the order of the arguments of the predicates is not what the mapping predicate expects, we would need to use either lambda expressions or auxiliary predicates. further contributing to verbosity (not the case here, however). Anyway, we could write something like:

file_url_attributes(File, Attributes) :-
	expected::from_goal(open(File,read,Stream), Stream, ExpectedStream),
	expected(ExpectedStream)::map(read_stream_to_codes, ExpectedCodes),
	expected(ExpectedCodes)::map(atom_codes, ExpectedAtom),
	expected(ExpectedAtom)::map(parse_url, ExpectedAttributes),
	expected(ExpectedAttributes)::or_else_throw(Attributes).

And then do:

?- catch(file_url_attributes(File, Attributes), Error, handler(Error)).

Where handler/1 would be defined as:

handler(abort) :- ...
...
handler(error(Formal,_)) :- handler_formal(Formal).

handler_formal(existence_error(file, ...)) :- ...
...

We could define some object aliases and predicate aliases for a bit more compact code. Still, this solution is arguably much cleaner than a sequence of conditionals, most of them doing step specific error catching and handling as in the skeleton code you wrote.

Hmm. In my opinion this is not a step forward. In good old plain Prolog I can still do

file_url_attributes(File, Attributes) :-
    setup_call_cleanup(
       open(File, read, In),
       (  read_stream_to_codes(In, Codes),
          atom_codes(Atom, Codes),
          parse_url(Atom, Attributes)
       ),
       close(In)).

This looks a lot better to me and closes the input stream, Where does that happen in your code?

Now it isn’t really a big thing to call

catch(file_url_attributes(File, Attributes), Error, handler(Error)).

The expected interface is nice in representing errors as unexpected normal data objects together with similar objects that are expected. So, the expected interface allows for a stream of data objects, some of which are expected and some not. In this example we just want to bail out on an error and the application may want to report certain errors friendly to the user and others adequately to the developers. You don’t want the user to see “Existence error parse_url/2. Please retry with a different file name” :slight_smile:

The main problem with the above code is that there are several types of exceptions and this catches all of them. In theory, handler/1 can re-throw the ones we are not interested in, but this severely hinders debugging in SWI-Prolog. If an error is uncaught, SWI-Prolog will record a stack trace and start the debugger asap. If the error is caught, SWI-Prolog assumes the application deals with it and thus the stack trace is not recorded and the debugger doesn’t kick in.

So, what I want is a library of related errors the application user might be interested in and an abstraction that allows me to specify I want to catch errors of this class (say file errors) and leave other errors unaffected as they do not carry anything the user can fix. This notably suffers from the fact that ISO error terms are organised quite differently. For example, we have existence_error(source_sink, Name) that tells me some file is missing as well as existence_error(procedure, Name/Arity) that tells me a predicate is missing. That is quite a different thing.

1 Like

You are taking my example literally where I’m explicitly trying to illustrate a class of problems where you have a sequence of steps where each step can act both on expected data or on an unexpected error or failure from the previous step (using other predicates from the expecteds library). Your alternative code doesn’t provide the same semantics (if you abstract the details of the example) as an error or failure at one of the steps doesn’t give a chance to the next step to handle it and instead jumps directly to the handler.

Thanks for clarifying.

Yip. The expected interface does allow for that and that is (sometimes) a nice idea. The traditional exception interface just aborts the computation up to some parent willing to deal with the error. That is often also a good plan. Neither addresses distinguishing who is interested in the error. That is probably even more severe for the expected interface. If some early step raises a predicate existence error and it pops up much later in the pipeline, you really have no clue where to start debugging.

That is the issue I would like to improve :slight_smile: