What is up with the cut (!)

Hmmm. Am I missing something? when/2 is a coroutining predicate. Value_base64 is surely unbound as it is the first appearance. If Value is ground, the when/2 executes base64_encoded/3 immediately. Else it will delaym but that means it will immediately call atomic_list_concat/3 with an uninstantiated Value_base64, resulting in an error.

Can you explain why a when clause was used here to begin with … What benefit is had?

Why should there be a Value (or Value_base64) that is uninstantiated in that call – what is the calling context?

Dan

Unsure whether this question is in the right thread, but I think it’s related. Suppose I have some complicated term in a defaulty representation, e.g. a mathematical expression like a^2.

Now I want to turn this into a clean representation, say xfy(^, symbol(a), integer(2)). I can write some code with a lot of cuts and metapredicates to do this. Would this picat style be a promising alternative?

It depends on the other terms you want to distinguish it from. =>/2 style matching help if the goal (input) arguments may contain variables that must be distinguished from known concrete values. So, this avoids handling variables.

 p([], ...) =>
 p([H|T], ...) =>

If you want to deal with an atom though, you get this,which I think is fine despite having a cut behind the scenes.

p(X), atom(X) => ...

Note that non-defaultly representations only make sense if you want to have code that is deterministic on given input and the same code can be used in reverse mode. If you do not want/need multi-moded code I can see no advantage on non-defaulty representations. In some Prolog systems, including SWI-Prolog you may profit from clause indexing. There is no fundamental reason why atom(X) cannot be taken into account by the clause indexing code. symbol(a) certainly needs to be created, scanned and garbage collected.

This is already a huge win. Specifically because => rules do not allow a silent fall-through.

This is the closest to Erlang “guards” that I have seen so far in Prolog, and I really like it.

PS: Erlang guards have requirements on what can be in this goal between the head and the neck. My impression is that the Prolog equivalent to this would be “the goal does not further instantiate its arguments”, like any of the type checks with atom/1, ground/1, is_list/1 and so on.

If I understand the => so far, it seems that there are no restrictions on instantiation, only a “hard cut” after it, like in these examples:

foo(X), X = a => true.
foo(X), X = b => true.

bar(X), between(1, 3, X) => true.

and then:

?- foo(X).
X = a.

?- foo(a).
true.

?- foo(b).
true.

?- bar(X).
X = 1.

?- bar(4).
ERROR: No rule matches bar(4)
1 Like

True. If I recall correctly the docs also claim this may not be a good idea. A general restriction “may not instantiate any of the goal arguments” would be great. This is not so easy to enforce. For the head unification we know that if some binding has been created we must fail. Prolog code for the guard may bind other variables and can still be perfectly fine. Checking that the goal arguments remain unchanged gets rather costly. An alternative could be a whitelist of allowed guards. That seems rather restricted though as libraries and extensions may provide perfectly sound guard predicates. I think I’m more in favor of future static analysis tools flagging dubious guards.

I don’t remember how exactly it goes with Erlang, but my impression was that allowed guards are indeed whitelisted. Does anyone know if user-defined guards are allowed? Reading in a hurry, it seems the answer to user-defined guards is NO!

Their docs say, “evaluation of a guard expression must be guaranteed to be free of side effects”.

I guess such static analysis can eventually become a part of the compilation process, once mature enough and proven to be useful? I like this approach.

1 Like

Value_base64 is not necessarily unbound if you are running assoc_metadata_parts “backwards”, in which case you deconstruct Meta to obtain Key and Value_base64 from atomic_list_concat/3 which accepts a mode of (-, +, +). Once it becomes bound from this, we calculate Value appropriately.

It also seems that without even giving it a name, the current implementation supports what Erlang calls “guard sequences”, with almost the same syntax :slight_smile:

A guard sequence is a sequence of guards, separated by semicolon (;). The guard sequence is true if at least one of the guards is true. (The remaining guards, if any, are not evaluated.)

foo(X), ( writeln(a), X = a ; writeln(b), X = b ) => true.

and then:

?- foo(a).
a
true.

?- foo(b).
a
b
true.

?- foo(c).
a
b
ERROR: No rule matches foo(c)
1 Like

Erlang doesn’t allow user-defined functions in guards, which was something that tripped me up learning it. Only a limited number of “BIFs” (Erlang jargon for builtins) are allowed to be used in guards, and figuring out which needs to be done by trial and error since it’s not well documented.

1 Like

Thank you, I’ll give it a try.

Besides bidirectionality, there is one more strength of non-defaulty representations, namely, what you mention, indexing. I noticed it when using trace. Very often, the interpreter runs deep into the tests in things like

rule(In, Out) :-

(all kinds of tests),

!,

do_something(In, Out).

Whereas

rule(symbol(X), Out) :-

do_something(In, Out).

The latter does not even leave a choice point. Unfortunately, the representation I have to use is defaulty. So I add an intermediate step in which everything is made translated to clean, and only then, the rules are applied. This translation is quite dirty (meaning: there are cuts), but I can live with that.

In the later course, I’ll ask again, since some of the do_something/2 rules are, themselves, defaulty represented. And indeed, they include variables.

Best wishes,

Matthias

Maybe this will ultimately get more interesting. For example we could envision a (default) mode where the debugger will jump immediately to the matching rule rather than tracing all the tests. We would get a similar result if most of the simple type tests are included in clause indexing … one day …

 rule(In, Out), (all kinds of tests)  =>
     do_something(In, Out).
2 Likes

I have noticed a few of these already, for example, things like

rule(In, Out) :-
compound(In),
(other tests),
!,
etc.

are skipped if In is not a compound.


Just quoting this because it seems to be a fundament truth that should be understood. By quoting it, it should make it more apparent to those less experienced with Prolog that knowing this is of value.

I have spent tens of hours trying to get many DCGs to work in multi-mode (both directions) only to give up and write separate predicates for the needed different modes. So will keep this idea in mind the next time I attempt code of the sort.

2 Likes

I am reading this and can’t make sense out of this … could you give an example.

Reading O’Keefe, non-defaulty is about enabling choices to become directly indexed rather than have them become implicity tests inside predicates.

I guess that is what is meant “on given input” – i.e. arguments that have the non-default shape, e.g. leaf(X) rather than a more generic node(X) or just X.

But, i guess, non-deterministic code would benefit from indexed choices as well.

And how does this specifically relate to reverse mode … and determinism.

All clarifications are welcome.

Dan

As a simple example, take the following predicate that translates a mathematical term into MathML (I am currently busy with this, therefore the example):

This is the defaulty version. Assuming you have written enough rules, you can use it to translate (defaulty) terms like a^2 + 2ab + b^2 to something that can be rendered by a browser.

mathml(X, M) :-
    atom(X),
    !,
    M = mi(X). % mi = "identifier"

mathml(X, M) :-
    number(X),
    !,
    M = mn(X). % mn = "number"

mathml(A^B, M) :-
    !,
    mathml(A, X),
    mathml(B, Y),
    M = msup([X, Y]). % msup = "power"

% etc. other rules

The code only works mathml(+, -), and you’ll see a lot of backtracking when you debug your code.

Now the clean version, which of course already requires a clean representation, for example, op(+, op(^, atom(a), number(2)) + …).

mathml(atom(X), mi(X)).

mathml(number(X), mn(X)).

mathml(op(^, A, B), msup([X, Y])) :-
    mathml(A, X),
    mathml(B, Y).

% etc. other rules

I admit that the example is a bit trivial since it essentially translates the names of the functors, but the code below works both ways (what Jan said) and leaves no choicepoints (which helps a lot while debugging). So, whenever possible, I try to compensate for prolog’s lack of types in the representation.

3 Likes

Great, thank you.

Why is it that this code leaves no choice points.

I guess it is because of the compound naming of the arguments and because indexing is able to identify that each mathml head with its first argument is mutually exclusive.

I think a summary document that presents when choice points are created and when they are not could be very important indeed to have.

Btw, as an aside – if i recall correctly in the craft of prolog O’Keefe argues against a construction of msup([X, Y]) in favor of a compound term msup(X, Y) based on space considerations - a list takes up more space and is slower to look up than a compound term.

I think msup(X, Y) is internally represented as an array, and [X, Y], as a linked list of cons.

Dan

One place to start reading: Just-in-time clause indexing.

1 Like

Note that this is not set in stone. Just about any Prolog will index on the first argument if this is an atom or integer or compound (in which case only the name and arity of the term is considered). Anything else varies from system to system and tends to get better

True. And msup(X,Y) does a much better job when it might apply to clause indexing. The rule is simple: only use lists for representing an unknown number of elements. Otherwise use a compound. Given SWI-Prolog’s unbounded arity for compounds and arg/3 that can be used to search this is generally convenient, compact and fast.

2 Likes