I’ve encountered some strange behavior that looks like bug or design flaw to me.
test :-
myscript(S), % Exception!
call(S).
get_dict(D) :- D = mydict{data : 100}.
myscript((get_dict(Dict), writef("%w\n", [Dict.data]))).
This code invoke dynamically two goals: to retrieve Dict from predicate and to print one of values.
But this code throws exception on myscript/1 invocation. It appears that it tries to preprocess “Dict.data” expression trying to query this field from still not instantiated variable Dict and fails with “Arguments are not sufficiently instantiated” in ‘.’/3 predicate.
Unless I’am missing something, this seems to me as big overlook in dict implementation.
Hmmm… I conclude that there must be some built-in facility to preprocess terms aside from standard term_expansion/2. As far as I know term_expansion and goal expansion are invoked only when loading prolog files, but not when you construct and call dynamic terms in run-time. Am I right?
I would not expect there to be a difference and in fact would want it to be consistent.
In your example you did not note how the code was used, e.g. was it put into a Prolog file and loaded, was it done using SWISH, was it created from the command line using :- user., e.g.
I think the term_expansion/2 for ./3 needs to be adjusted. However what I think should be done and what Jan W. does are not always the same, so waiting to see what he notes.
Possibly the built-in term expansion should look for a goal being declared as a meta predicate and do something different in that situation. But this might not be easy to solve – ISTR that there are some situations where library(yall) is slightly different depending on whether the code is compiled or dynamic.
There are also some other built-in term expansions, such as for DCGs.
I fear it is sort of unavoidable unless we add typing or some syntax to dis/enable conversion explicitly. It is designed to work with e.g.
p(Dict, Dict.data).
Which will give the right result if you do e.g.
?- get_dict(Dict), p(Dict, Data).
Dict = ..
Data = 100.
As is, there is no “quoting” mechanism for X.Y, so if you really want to create such term you only have a runtime solution, e.g. functor(Term. ['.',X,Y]), so you get the somewhat ugly
But now, you are still not there as you need the expansion before calling, so you need expand_goal/2 before using call/1. on the thing.
Note that, as rewriting ./2 is at the end of the source translation, you can write your own term_expansion/2 rules to automatically translate the myscript/1 clauses.
As said, I think either typing that can indicate that the argument of myscript needs special treatment or quoting are the only ways out. If anyone sees something better, stand up.
At present I don’t as I did not look into this past the point of locating the term_expansion/2 code.
Appreciate your notes, was not expecting any mention of typing but if typing means what I think you mean then it is a very deep rabbit hole that I prefer not to broach. Also having worked with quoting that is another potential deep rabbit hole I prefer not to broach.
That is in general not a good idea. It would leave Dict.key as a dangling constraint in case the Dict is unbound. Constraints are nice, but they must be used with care and not introduced without the user being aware.
Yes. Turn the ./2 term into something else. Note that the term_expansion/2 code that does the matching cannot use ./2 terms either, so you must use functor/3, etc. The dict expansion code is done later.