When does assert fail? Running out of space?

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

I think it’s more a case of adopting good spelling and grammar rules than right or wrong. Sadly, especially in English, one just has to learn by rote the way a word is spelt irrespective of how unlogical the spelling is so as to be clear to other people.

A quote I recently came across which I really like is “Example is the school of mankind, and they will learn at no other” attributed to Napoleonic-era British politician Edmund Burke.

Ultimately, I think what is needed in any kind of language is good examples which people can use to avoid constantly making up their own spelling and punctuation rules.

Reading medieval texts is difficult because no two writers spelt things the same way, and punctuation was made up on the fly. Establishing rules and teaching them made literacy a lot easier. Sadly, we’re stuck with a lot of silly spelling and grammar rules because of ancient history, a problem modern computer languages haven’t avoided.

I’m in the process of learning Erlang, and something I like is programmers are encouraged to follow one of three design patterns, each of which expect the programmer to create modules following a certain template with the required function names and arguments hardcoded.

This paint by numbers approach will frustrate anyone trying to do anything very original. But as a novice, I find the approach of fleshing out templates developed by experienced programmers allows me to get going with big projects instead of getting stuck in the shallows while trying to re-invent the wheel very useful.

I must admit that I have never read that book, but I do know F# and the author Scott Wlaschin is highly regarded in the F# community along with his web site F# for fun and profit

As to minimizing perplexity for me it encompasses more than just minimizing perplexity in just programming but all logical aspects in life.

As I recently noted

Confusion is also my companion whom with serendipity will introduce me to ideas. So having one without the other makes life dull.

In other words, I tend to wonder off the path often when looking for solutions, but read enough about the ideas so that I understand the vocabulary of the topic and enough of the ideas that my mind can ponder about them when I am not actively thinking about them. Then as time goes by (decades) ideas that seem to have no connection suddenly make a connection and then it starts to make more sense, e.g.

never really understood why they called the concept sort even though they explained it, but in reading about your problem it makes more sense.

Another thing that helps is to have a healthy set of ways to tackle problems. If you look in the Bug hunting toolbox you will find

Advise: For brain storming ideas read " How to solve it : a new aspect of mathematical method" by George Pólya (WorldCat) (partial pdf)

which is where I turn when I am really stuck.

However it does not have one way that I like: Find a much harder problem and work on that for a while then come back to the original problem and you will be happier. For me I like to try and learn quantum mechanics on my own as the harder problem. See: Introduction to Mathematical Physics

In doing one project that took a few months several years ago I learned that one of the hardest parts of learning to program is not the coding, or trying to figure out the algorithm needed but in understanding the ethos of the programming language. Many programmers will form a silo around a specific language and tend to stay with just that language and they form an ethos. If however you move around into various languages to use the strength of that language you have to change your way of thinking to work with the ethos of those programmers. If you have ever tried to use a language to install a package in more than one language you will see that almost every popular language has a different package manager.

Another thing I regularly do is help others in forums like this or StackOverflow but try and answer questions that are a bit out of my normal area, in the last 24 hours other than in this forum I answered,

How can I extract embedded fonts from a PDF as valid font files?

by doing this it keeps my mind open and exposes me to other ideas and often the OP of the question will help me to understand their way of seeing things.

I could write a book on this topic, but HTH. :smile:

Hi Jan,

Here are two blogs that relate to Expected and Optional.

The general idea is have (meta) predicate constructs that return to callers possibilities / options with related predicates to handle them – one possibility is a valid return and another an error.

What makes this useful, is that error handling is pushed from the inside to the outside via dynamic structures and threading.

This approach has apparently seen a lot of uptake in the functional / Domain Driven Design (DDD) community … with (buzz) words such as monads and others … making the rounds.

Edit: there were also major successes, such jet.com, an amazon-like commercial website, written in F# and a DDD approach, that was purchased by Walmart – providing evidence that this approach scales to the web.

(https://techcrunch.com/2016/08/08/confirmed-walmart-buys-jet-com-for-3b-in-cash/).

https://logtalk.org/2019/11/21/handling-missing-data.html

https://logtalk.org/2019/11/27/handling-optional-data.html

I use to hang out with some of them at F# meetups on Saturdays in NYC. Jet.com is a small tight group who helped to push F# into industrial use.

an A-team :slight_smile: – that gets things done.

Well, if my fledgling venture succeeds- perhaps, i can put together such an A-team to push Prolog into industry … :slight_smile:

This is not to step on Dans toes, but hopefully to give some more info to others trying to understand the idea.

See: Railway Oriented Programming

I know it uses F#, but the concept is fairly simple and if you have questions, ask and hopefully I can answer.

Thanks. I get the idea. Took a brief look at the implementation. This strikes me a bit:

	from_goal(Goal, Value, Error, Expected) :-
		(	catch(Goal, Ball, true) ->
			(	var(Ball) ->
				Expected = expected(Value)
			;	Expected = unexpected(Error)
			)
		;	Expected = unexpected(Error)
		).

This means that a from goal is forced the semantics of a deterministic predicate. That is a very procedural way to view the world. Alternatively we could use

from_goal(Goal, Value, Error, Expected) :-
    catch(Goal, E, true),
    (    var(E)
    ->  Expected = expected(Value)
    ;    Expected = unexpected(Error)
    ).

and we could have e.g.

multi(Goal) :-
       (    Goal
       *->  true
       ;    throw(error(failure_error(Goal), _)).
       ).

one(Goal) :-
      (   Goal
      ->  true
      ;   throw(error(failure_error(Goal), _))
      ).

Now we can do e.g.,

 from_goal(one((parent(Child,Parent), male(Parent))), missing_father, ExpectedFather).

And, I’d turn the unexpected term into a two-argument term such that we can pass along both the missing value and the exception.

The overall idea to carry exceptions along as normal values is interesting though. Funny enough, especially for Prolog where often have a stream of answers from a non-deterministic predicate which you may not want to abort due to a local exception but for which you cannot make a decision locally. It is related to delimited continuations, which allow the location where something unexpected happens to using shift/1 to tell the environment something action is needed. So we get something like

parents_of(Person, Father, Mother) :-
      (   parent(Person, Father), male(Father)
     ->   true
     ;    shift(missing_father(Person, Father))
     ).

Now the environment can (for example) do this to fill missing fathers as unknown

reset(Goal, missing_father(_Person, Father), Continuation),
(    Continuation == 0
->   Father = unknown
;    call(Continuation)
)

I vaguely recall XSB? Ciao? also provide a library that allows defining a context in which certain unexpected events are handled in a specific way.

1 Like

The from_goal/3-4 library predicates are indeed deterministic but they are not the only constructors of expected terms provided by the library. Nothing forces those semantics or that view. E.g.

my_nondet_from_goal(Goal, Value, Error, Expected) :-
    catch(Goal, E, true),
    (    var(E)
    ->  expected::of_expected(Value,Expected)
    ;   expected::of_unexpected(Error,Expected)
    ).

But note that this alternative based on your code misses an important case: often you also want to construct an expected term when the goal fails.

That said, adding non-deterministic versions of the from_goal/3-4 predicates could be useful. Thanks for the feedback.

Given that the unexpected error representation is up to the user, nothing prevents using e.g. a compound term for its representation for carrying whatever information you want. It’s a wrapped term. What you wrap is up to you.

Delimited continuations are only found in a few systems. Not a viable basis for a portable library.

Except that as I look at from_goal/4, you cannot get hold of the Ball :frowning: If you use

 unexpected(UserValue, Ball)
 unexpected(UserValue, fail)

and some method(s) to get hold of the second argument you can give more informative feedback if desirable.

Sure :slight_smile: The Ciao/XSB (I think Ciao) alternative doesn’t need anything non-portable AFAIK. The idea is a little different though: the inner level raises a problem that is handled depending on the context, allowing the context (for example) to fill a special value, fail (ignore the solution) or throw an exception and abort the whole thing. I’ll see whether I can find the reference …

[Edit] Got it. It is called interception/3 and send_signal/1. See https://ciao-lang.org/ciao/build/doc/ciao.html/exceptions.html

In a sense I like this better than the expect API. It is simpler and doesn’t interfere with (non-)determinism.

Note that this stuff also relates (somewhat) to SWI-Prolog’s library(broadcast)

Again, from_goal/3-4 are not the only constructors provided by the library. If you need something different, then you can construct it from the basic of_expected/2 and of_unexpected/2 constructors.

Sigh. This is like claiming that the presence of, say, lists:selectchk/3 interferes with (non-)determinism of the lists library.

And to Logtalk support for event-driven programming. Still, however related all these concepts are, they ultimately provide solutions for different problems.

True. I’m not really sure whether I’m happy with so many alternatives that have such a large overlap. I guess your blog on best practices is a good step to help users!

P.s. we even forgot Paul Tarau’s interactors/engines, which are also quite useful for related tasks …

1 Like

To add my two cents – while fully being aware that this discussion is way above my head …

Curious – to see the catch wrapped into the from goal – i always looked at it from the throw side – to wrap the exception into an Expected and continue the flow of processing until it returns to the caller who then decides – possibly, non-determinstically - what to do.

The key benefit – and the one i was seeking – is that one does not need throw / catch to deal with exceptions, which makes the code simpler to read and write.

Now, there are exceptions that are thrown such as memory exceptions – these would then indeed need some kind of catch wrapping – to integrate catch / throw into the Expected approach.

Dan

Yes parts of it are also over my head, e.g. delimited continuations

In reading some of the post it seems the discussion has expanded into trying to add a new/additional means of handling exceptions/errors into the current style of Prolog in such a way as not to use the try/catch mechanism.

However that seems more than what you seek as you are hoping for something that resembles railway-oriented programming, (please correct me it that is wrong) or in the terms as stated here you seek

In understanding and having used railway-oriented programming in F# some of the finer concepts that need to be considered:

  1. If the data is being processed like a user entering a command and then if the command is invalid and an error can be returned, then the examples given in the F# video work great.

  2. If the data is being processed like the parsing of source code and you want to parse the entire file, collect any errors along the way in the error part of the two part term and be able to process the entire source code, then that also works nicely because the errors just accumulate into a homogeneous list.

  3. If the data is being processed like a build tool of compiling different directories and putting together different parts that don’t fit into a homogeneous list of errors, then the error part of the two part term turns into a nested structure that gets more complicated as the parts that are processed get added. Then if the build tries to be intelligent about looking into the error structure to try and fix the errors to continue, now even though adding the new form of error handling makes the happy path look nice, the fix up code can become just as entangled as the original code that used the try/catch.

    I am not saying there are not ways to handle this, but you should think about this as you think about scenarios of usage; I agree with Jan W. that you must consider the non-deterministic paths and heterogeneous error results when trying to solve this problem.

There are several aspects of an implementation of an expected monad that only a closer look can clarify. For example, consider Jan’s scenario:

The UserValue is typically bound by the goal. If the goal fails or throws, them UserValue is unbound and neither unexpected(UserValue, Ball) or unexpected(UserValue, fail) give more information than unexpected(Ball) or unexpected(fail).

Cursory glance at the particular Logtalk implementation of this monad also misses that, for the from_goal/4 predicate, its description includes Otherwise returns a reference with the unexpected goal error or failure represented by the Error argument. If the actual exception is sought, there’s from_goal/3 for that. But these are just two of the provided constructors.

A key point is that the library is not just about exceptions but also about (unexpected) failures. The same library can be used to handle both failures and errors as unexpected events, thus simplifying coding.

Thank you for the experience report …

So, a, so called, pipe and filter architecture is well suited for this approach, but, a more interactive (blackboard?) architecture would have much variation mirrored in the expected code structure …

In my own personal experience and view with railway-oriented programming in working with F#, specifically using algebraic data types I have learned to not focus on the way the error part of the data type (or term in Prolog) is constructed (how it originated), but what the structure of the error part of the data type is, e.g

  • None
  • Scalar
  • Homogeneous list
  • Heterogeneous list
  • A user defined type (F#: algebraic data type) (Prolog: compound term)

While constructing the error part is easy, depending on what you want to do with it can be as simple as ignoring it to as complicated as trying to dissect it and correct an error without user intervention. It is when the error part is extremely complex and you want the code to correct the error without user intervention that things get messy again on the unhappy path. The good thing is that the happy path still looks nice.

I can’t say how much you may run into the scenario I have noted, but just be aware of it when considering railway-oriented programming. My personal take is that unless you have a valid reason to not use it, and it gets you to a happy path then use it.

Another benefit of it is that you can divide up the design and implementation of software with the happy path going to a more junior programmer and the error fix-up, if needed, going to a more senior programmer. Also it makes eyes on understanding of the code so much faster. Note the saying when writing code, “It will be read a lot more than changed, so make it easy to read” which is what the happy path does.