When does assert fail? Running out of space?

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:

It’s now clear to me that’s the main problem that you are trying to solve.

It would depend on how much (or how little) details of the term representing the unexpected error or failure would carry. When calling built-in predicates with their fixed exceptions, that would indeed be an issue. With user-defined predicates, you can plan for richer representations that could make use of some registry that associates classes or errors with interested parties.

In trying to bridge the idea of railway-oriented programming with Prolog, and with railway-oriented programming originating in F# which is a functional language I have turned to papers that talk about the implementation of functional-logic languages for ideas. This is not to suggest anything at this point, but just to note that these papers get the ideas flowing, e.g. it seems that we are defaulting to the depth-first strategy of Prolog, perhaps a breath-first search is needed for some of the processing (just an idea that popped into my mind when reading a paper).

One I am currently reading, although a bit old is:

THE RELATION BETWEEN LOGIC AND FUNCTIONAL LANGUAGES: A SURVEY

1 Like

It’s already there via Logtalk. Notably, you can use most Logtalk libraries, including the expecteds library, from within plain Prolog or a Prolog module in the same way that you can use Prolog built-in predicates and most Prolog module libraries from within a Logtalk object.

Drawing lines in the sand and acting like there’s a wall serves no useful purpose. In the context of a Prolog system that touts a pack infrastructure and invites contributions, is quite odd. In this particular thread, where we are discussing different semantics for exception handling, portraying those solutions as us versus them it’s just distracting.