Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No easy way to nullify a term using term_expansion without hacks #2623

Open
jjtolton opened this issue Oct 17, 2024 · 16 comments
Open

No easy way to nullify a term using term_expansion without hacks #2623

jjtolton opened this issue Oct 17, 2024 · 16 comments

Comments

@jjtolton
Copy link

jjtolton commented Oct 17, 2024

Update: Workaround

user:term_expansion((:- womp), (:- initialization(true))).

tl;dr

There should be some way to nullify terms in term expansion without needing to result to (:- module(random_name, []), probably by doing user:term_expansion(<pattern>, []).

Overview

There is no easy way to nullify terms using term expansion, and the same is probably true for goal expansion though I haven't tested it yet.

By nullify I mean "expand to zero terms". So to nullify term (b) from the following:

a.
b.
c.

We would expect the nullification of (b) to result in:

a.
c.

Motivation

I like to add the following to my scripts these days:

user:term_expansion((?- Term), (:- module(a, [])).

The way I don't get the following warning in commented queries
There should be some way to nullify terms in term expansion without needing to result to (:- module(random_name, []), probably by doing user:term_expansion(<pattern>, []).

:

% Warning: overwriting ?-/1 because the clauses are discontiguous

You can also use the same technique to make custom :- directives:

user:term_expansion((:- setup_table), (:- module(a, []))) :-
             empty_assoc(Assoc),
             bb_put(my_table, Assoc).
:- setup_table.

(this technique is necessary because :- initialization(something) comes after term_expansion, so if you want to used initialized state, you must initialize the state in the term expansion lifecycle phase rather than then initilization lifecycle phase)

Explanation of Workaround

Note the hack: (:- module(a, []). That's because this is the only way I know of to NULLIFY a term, equivalent to a comment macro in lisp:

(defmacro comment (&rest body) "Comment out one or more s-expressions." nil)

;; this all gets ignore completely when "consulting" the lisp file
(comment
   (foo a b) 
   )

Exploration of Current Behavior

These are the following techniques you would probably expect to work in Prolog:

user:term_expansion((?- Term), _) :- false.

Doesn't work, the term will remain unchanged and then eventually called as is resulting in the same warning. (Note: this is expected, I think. I don't think false should nullify the term).

The following should probably splice or expand into 0 terms in place of the current term:

user:term_expansion((?- Term), []).

however it results in the following warnings:

% Warning: overwriting []/0 because the clauses are discontiguous

which indicate to me that [] is being treated as a fact.

You can get the same warning by doing:

[].

a.

[].

Expectation vs Reality

The net result of this is that you can't make a side-effecting user-defined directive with the :- operator because you get a domain error:

:- womp.

results in

error(domain_error(directive,todo_insert_invalid_term_here),load/1).

unless you do something like:

user:term_expansion((:- womp), (:- module(a, []))) :- 
    <side effects>.

You can't simply do

user:term_expansion((:- womp), _) :- 
     <side-effects>,
     false.

because that will still result in an unchanged (:- womp) which results in

error(domain_error(directive,todo_insert_invalid_term_here),load/1).

Expectation

There should be some way to nullify terms in term expansion without needing to result to (:- module(random_name, []), probably by doing user:term_expansion(<pattern>, []).

@triska
Copy link
Contributor

triska commented Oct 17, 2024

You can expand it for example to:

nullified :- false.

Or use for example the predicate name '$nullified' as a more "internal" name that is less likely to conflict with existing predicate names.

@hurufu
Copy link
Contributor

hurufu commented Oct 17, 2024

Nullification works for me:

?- [user].
term_expansion(b, []).
a.
b.
c.


?- a.
   true.
?- b.
   error(existence_error(procedure,b/0),b/0).
?- c.
   true.
?- 

@triska
Copy link
Contributor

triska commented Oct 17, 2024

@hurufu: No, it has only replaced b by [] (which currently we cannot call, but it works in rebis-dev!).

You can see the issue with:

term_expansion(b, []).
a.
b.
c.
b.

yielding:

% Warning: overwriting []/0 because the clauses are discontiguous
   true.

@hurufu
Copy link
Contributor

hurufu commented Oct 17, 2024

@hurufu: No, it has only replaced b by [] (which currently we cannot call, but it works in rebis-dev!).

You can see the issue with:

term_expansion(b, []).
a.
b.
c.
b.
yielding:

% Warning: overwriting []/0 because the clauses are discontiguous
true.

Wow, indeed. Silly me. I think it should count as a bug. Then yes, the only solution I see is currently to expand to known "bad" predicate, as you have proposed. Or even better to some undefined dynamic predicate, so it would fail faster.

UPDT: As an example I've tested this behavior on all prologs that I'm currently being packaging.
Here is the result: https://gist.github.com/hurufu/a88137d5ba86189119fb96f8ce7c11af

It looks like every Prolog handles it differently

@triska
Copy link
Contributor

triska commented Oct 17, 2024

@hurufu: I think the intuition is justified though, that an expansion of [] results in a vanishing definition, because the definition of the fact [] seems extremely unlikely to be intended.

@UWN
Copy link

UWN commented Oct 17, 2024

A fact [] can still be written as []:-true.

Note that in SICStus, a single []. is ignored. And a list of terms is just handled as a sequence of such terms.

| ?- [user].
% compiling user...
| [f(1),f(2)].
| 
% compiled user in module user, 44 msec 516288 bytes
yes
| ?- f(X).
X = 1 ? ;
X = 2 ? ;
no

In SWI, however no longer:

Welcome to SWI-Prolog (threaded, 64 bits, version 9.3.13)
...
?- [user].
|: [f(1),f(2)].
|: ^D% user://1 compiled 0.00 sec, 4 clauses
true.

?- f(X).
X = 1 ;
X = 2 ;
X = 1, unexpected ;
X = 2, unexpected.

@hurufu
Copy link
Contributor

hurufu commented Oct 17, 2024

Something like this should work and be very fast:

:- initialization(abolish(nil/0)).
:- dynamic(nil/0).

term_expansion(b, nil).

a.
b.
c.
b.

@jjtolton
Copy link
Author

Something like this should work and be very fast:

:- initialization(abolish(nil/0)).
:- dynamic(nil/0).

term_expansion(b, nil).

a.
b.
c.
b.

I didn't even realize until just now that you could run term_expansion/2 from the toplevel :O

@triska
Copy link
Contributor

triska commented Oct 17, 2024

@hurufu: An ideal solution would not affect any existing predicate definitions, if such definitions are also present in the source file.

@hurufu
Copy link
Contributor

hurufu commented Oct 17, 2024

I didn't even realize until just now that you could run term_expansion/2 from the toplevel :O

My example didn't contain code executed in a toplevel, or was it just an observation?

An ideal solution would not affect any existing predicate definitions, if such definitions are also present in the source file.

then fixing behavior of term_expansion(b, []) is the way to go, IMHO, and if you really want to expand b/0 to []/0 then write something like term_expansion(b, [[]])..

@triska
Copy link
Contributor

triska commented Oct 17, 2024

then fixing behavior of term_expansion(b, []) is the way to go, IMHO

Yes, I agree! This means the empty list of terms, so vanishing.

@jjtolton
Copy link
Author

user:term_expansion((:- womp), (:- initialization(true))).

this is turning out to be the best workaround so far!

@jjtolton
Copy link
Author

I'll leave this open because I assume that my workaround is probably not considered "acceptable" but it's certainly good enough for me.

@hurufu
Copy link
Contributor

hurufu commented Oct 18, 2024

user:term_expansion((:- womp), (:- initialization(true))).

This solution will slightly increase startup time if you nullify a lot of terms.

@jjtolton
Copy link
Author

jjtolton commented Oct 18, 2024

user:term_expansion((:- womp), (:- initialization(true))).

This solution will slightly increase startup time if you nullify a lot of terms.

I know you are used to thinking of startup performance down to the clock cycle level but my comparisons are to Unity, Java, and LLM startup speeds haha. For me I'm willing to take the hit!

@jjtolton
Copy link
Author

user:term_expansion((:- womp), (:- initialization(true))).

this is turning out to be the best workaround so far!

So this turns out not to be a great approach. Until we can nullify terms, I think it's better to use a custom operator directive.

:- op(1200, fx, :->).

':->'(_).

user:term_expansion((:-> womp), (:-> not_womp)) :- 
        do_stuff.

Using :- initialization(true) multiple times seems to make files discontinuous or have other strange effects that I haven't had time to dig into.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants