When does assert fail? Running out of space?

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 Exception and signal handling — The Ciao System v1.22

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.

I think what you are describing are key software engineering benefits …

Keeping the happy path clean – also plays into the domain driven development approach, where the code is, essentially, the model – non determinism and a relational programming should make this even more powerful.

Dealing with error can get complex, but at least this complexity is kept elsewhere … and outside of the system “kernel” that deals with the domain model.

Dan

1 Like

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