Strange dict error

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.

1 Like

listing(myscript) gives a hint about what’s going on.

I found that same hint using gtrace/0. If I followed the code generation correctly it lead me to term_expansion/2 for ./3, I.e. Dict.data

The real question is what does Jan W. think. Is it a bug, an incorrect way to write the Prolog code or something else.

At that point just set it aside until Jan W. replies.

1 Like

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?

Why would you think that?

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.

?- [user].
|: get_dict(D) :- D = mydict{data : 100}.
|: 
% user://2 compiled 0.00 sec, 1 clauses
true.

?- listing(user:get_dict/1).
get_dict(mydict{data:100}).

true.

Note: The demonstration for [user] did not enter the complete example, it was only to demonstrate how to use [user] from the top level.

When I verified your results I used the [user] means.

Now I get it. Perhaps, my problem could be solved if dict access resolution could be defferred until free variable is unified to some value.

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.

1 Like

freeze/2 might do what you want.

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

myscript((get_dict(Dict), writef("%w\n", Getter))) :-
    functor(Getter, ['.', Dict,data]).

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.

What about using freeze to defer actual operation on Dict until variable is instantiated?

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.

1 Like

Can I override standard dict term_expansion by writing my own term_expansion?

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.

1 Like