Somewhat related. @jschimpf, you are using X{attr1:V1, …} to read attributed variables. What does that mean if a variable already has attributes. E.g.,
?- write(X{a:1}), write(X{b:2}).
And also if there are different attribute values:
?- write(X{a:1}), write(X{a:2}).
Also, does your cycle trick with the = attribute imply there can not be any other attributes? I suspect that is the case as X{= : f(X)} is a ground term.
The result in all cases should be as if you had started with different variables having only a single attribute each, and then unified them. E.g. the first example should give the same result as
Apologies, I should have refreshed my memory before answering this – the above is not how it is done in ECLiPSe. The reasons became apparent in our discussion yesterday: the need to run attribute handlers inside the read/2, the possibility of these failing, etc.
Instead, the ECLiPSe syntax (from the early ‘90s…) stipulates that only one occurrence (no matter which) of each variable in a term can have the attribute annotation, repeat occurrences must be the plain variable. Everything else is a syntax error. So, for example
foo(X{a:1,b:2}, X).
foo(X, X{a:1,b:2}).
are both valid an denote the same term, while
foo(X{a:1}, X{b:2}).
is a syntax error[1]. This still seems to be a reasonable solution to me.
Though the error reporting seems broken in recent ECLiPSe versions… ↩︎
Ok. I’d implemented what we discussed, but that is probably not too hard to update.
I’m a little worried about writing. write_term/2 gets more complicated as it needs to track the variables for which it has written attributes. I guess one dirty trick would be to add a variable stating the attributes are already printed and backtrack to undo this? Or do you maintain a table?
The second worry is if you use partial writes, for example to do pretty printing. (How) did you resolve that?
If we use the unification route, writing the attribute set twice is typically not a problem as unifying two variables with the same set of attributes (and values) should typically be safe.
But yes, failure is one issue. I could still fix that using an error, but what if the unification hook is non-deterministic? A non-deterministic read/1 is a bit hard to grasp
Another issue is whether in Var{= : Value} it is allowed to have other attributes. It is pretty much useless. You could interpret it as “set constraints, then unify value”, but I cannot see any practical value. I’m tempted to turn additional attributes into an error. What do you think?
What do you do with repeated attributes, e.g., X{x:1, x:2}? Is it an error?
it seems there is nothing to be gained from forbidding more than one attribute annotation per variable. I’m therefore removing this restriction in ECLiPSe;
it makes sense to forbid multiple occurrences of the same attribute name (X{a:1, a:2} , X{a:1}-X{a:2} and even X{a:1}-X{a:1}), because the meaning of such constructs is difficult to define, depends on the semantics of the individual attributes, and could lead to failure or error;
different attributes should simply be added under the assumption that they are independent – no handlers/hooks are executed, no failure is thus possible;
as an exception to (3), the quasi-attribute ‘=’ is only allowed to occur on its own, as it is clearly not independent from other attributes.
Apart from checking the restrictions (2) and (4), the implementation then consists in simply invoking add_attribute/3 (ECLiPSe), put_attr/3 (hProlog, SWI) or put_atts/2 (SICStus) for each attribute occurring in the read term.
I’ll address the write-related issues in a separate post.
This is the same problem as with printing cyclic/shared subterms: you want to print them only once. ECLiPSe uses a marker bit for attributed variables, and more recently, a table for cyclic terms.
This is really a case of packing conflicting features into write_term. On one hand, it is useful to be able to see the printed term as a whole (e.g. for detecting sharing, detecting singleton variables, multiple occurrences of attributed variables, numbervaring), on the other hand it is nice to be able to delegate subterm printing (e.g. portray, attributes) or combine writexxx-predicates to print parts of a larger term separately.
To resolve that cleanly would require passing around a state-accumulator, but the interfaces are not designed for that. I’m not sure I want to worry about this though – the use cases probably don’t overlap much, e.g. when pretty-printing a clause you don’t expect cycles or attributed variables.