SICStus Style attribute declarations¶
SICStus-Prolog interface to attributed variables (currently unsupported).
The YAP library atts
implements attribute variables in the style of SICStus Prolog. Attributed variables work as follows:
-
Each attribute must be declared beforehand. Attributes are described as a functor with name and arity and are local to a module. Each Prolog module declares its own sets of attributes. Different modules may have attributes with the same name and arity.
-
The built-in put_atts_50 "put_atts/2" adds or deletes attributes to a variable. The variable may be unbound or may be an attributed variable. In the latter case, YAP discards previous values for the attributes.
-
The built-in @ref get_atts_50 @"get_atts/2" can be used to check the values of an attribute associated with a variable.
-
The unification algorithm calls the user-defined predicate @ref verify_attributes_51 @"verify_attributes/3" before trying to bind an attributed variable. Unification will resume after this call.
-
The user-defined predicate @ref attribute_goal_50 @"attribute_goal/2" converts from an attribute to a goal.
o+ The user-defined predicate @ref project_attributes_50 @"project_attributes/2" is used from a set of variables into a set of constraints or goals. One application of @ref project_attributes_50 @"project_attributes/2" is in the top-level, where it is used to output the set of floundered constraints at the end of a query.
Attributes are compound terms associated with a variable. Each attribute has a name which is private to the module in which the attribute was defined. Variables may have at most one attribute with a name. Attribute names are defined through the following declaration:
@icode :- attribute AttributeSpec, ..., AttributeSpec. @endicode
where each AttributeSpec has the form ( Name/ Arity). One single such declaration is allowed per module Module.
Although the YAP module system is predicate based, attributes are local to modules. This is implemented by rewriting all calls to the built-ins that manipulate attributes so that attribute names are preprocessed depending on the module. The user: \@ref goal_expansion_51 \@"goal_expansion/3" mechanism is used for this purpose.
The attribute manipulation predicates always work as follows:
- The first argument is the unbound variable associated with attributes,
- The second argument is a list of attributes. Each attribute will be a Prolog term or a constant, prefixed with the + and - unary operators. The prefix + may be dropped for convenience.
The following three procedures are available to the user. Notice that these built-ins are rewritten by the system into internal built-ins, and that the rewriting process depends on the module on which the built-ins have been invoked.
-
The user-defined predicate predicate @ref verify_attributes_51 @"verify_attributes/3" is called when attempting to unify an attributed variable which might have attributes in some Module.
-
Attributes are usually presented as goals. The following routines are used by built-in predicates such as @ref call_residue_50 @"call_residue/2" and by the Prolog top-level to display attributes:
-
Constraint solvers must be able to project a set of constraints to a set of variables. This is useful when displaying the solution to a goal, but may also be used to manipulate computations. The user-defined @ref project_attributes_50 @"project_attributes/2" is responsible for implementing this projection.
The following examples are taken from the SICStus Prolog manual. The sketches the implementation of a simple finite domain solver. Note that an industrial strength solver would have to provide a wider range of functionality and that it quite likely would utilize a more efficient representation for the domains proper. The module exports a single predicate domain( -Var, ?Domain) which associates Domain (a list of terms) with Var. A variable can be queried for its domain by leaving Domain unbound.
We do not present here a definition for @ref project_attributes_50 @"project_attributes/2". Projecting finite domain constraints happens to be difficult.
@icode :- module(domain, [ @ref domain_50 @"domain/2"]).
:- use_module(library(atts)). :- use_module(library(ordsets), [ @ref ord_intersection_51 @"ord_intersection/3", @ref ord_intersect_50 @"ord_intersect/2", @ref list_to_ord_set_50 @"list_to_ord_set/2" ]).
:- attribute @ref dom_49 @"dom/1".
verify_attributes(Var, Other, Goals) :- get_atts(Var, dom(Da)), !, % are we involved? ( var(Other) -> % must be attributed then ( get_atts(Other, dom(Db)) -> % has a domain? ord_intersection(Da, Db, Dc), Dc = [El|Els], % at least one element ( Els = [] -> % exactly one element Goals = [Other=El] % implied binding ; Goals = [], put_atts(Other, dom(Dc))% rescue intersection ) ; Goals = [], put_atts(Other, dom(Da)) % rescue the domain ) ; Goals = [], ord_intersect([Other], Da) % value in domain? ). verify_attributes(, , []). % unification triggered % because of attributes % in other modules
attribute_goal(Var, domain(Var,Dom)) :- % interpretation as goal get_atts(Var, dom(Dom)).
domain(X, Dom) :- var(Dom), !, get_atts(X, dom(Dom)). domain(X, List) :- list_to_ord_set(List, Set), Set = [El|Els], % at least one element ( Els = [] -> % exactly one element X = El % implied binding ; put_atts(Fresh, dom(Set)), X = Fresh % may call % @ref verify_attributes_51 @"verify_attributes/3" ). @endicode
Note that the implied binding Other=El was deferred until after the completion of \@ref verify_attribute_51 \@"verify_attribute/3". Otherwise, there might be a danger of recursively invoking \@ref verify_attribute_51 \@"verify_attribute/3", which might bind Var, which is not allowed inside the scope of \@ref verify_attribute_51 \@"verify_attribute/3". Deferring unifications into the third argument of \@ref verify_attribute_51 \@"verify_attribute/3" effectively serializes the calls to \@ref verify_attribute_51 \@"verify_attribute/3".
Assuming that the code resides in the file domain.yap, we can use it via:
@icode | ?- use_module(domain). @endicode
Let's test it:
@icode | ?- domain(X,[5,6,7,1]), domain(Y,[3,4,5,6]), domain(Z,[1,6,7,8]).
domain(X,[1,5,6,7]), domain(Y,[3,4,5,6]), domain(Z,[1,6,7,8]) ?
yes | ?- domain(X,[5,6,7,1]), domain(Y,[3,4,5,6]), domain(Z,[1,6,7,8]), X=Y.
Y = X, domain(X,[5,6]), domain(Z,[1,6,7,8]) ?
yes | ?- domain(X,[5,6,7,1]), domain(Y,[3,4,5,6]), domain(Z,[1,6,7,8]), X=Y, Y=Z.
X = 6, Y = 6, Z = 6 @endicode
To demonstrate the use of the Goals argument of @ref verify_attributes_51 @"verify_attributes/3", we give an implementation of @ref freeze_50 @"freeze/2". We have to name it \@ref myfreeze_50 \@"myfreeze/2" in order to avoid a name clash with the built-in predicate of the same name.
@icode :- module(myfreeze, [ @ref myfreeze_50 @"myfreeze/2"]).
:- use_module(library(atts)).
:- attribute @ref frozen_49 @"frozen/1".
verify_attributes(Var, Other, Goals) :- get_atts(Var, frozen(Fa)), !, % are we involved? ( var(Other) -> % must be attributed then ( get_atts(Other, frozen(Fb)) % has a pending goal? -> put_atts(Other, frozen((Fa,Fb))) % rescue conjunction ; put_atts(Other, frozen(Fa)) % rescue the pending goal ), Goals = [] ; Goals = [Fa] ). verify_attributes(, , []).
attribute_goal(Var, Goal) :- % interpretation as goal get_atts(Var, frozen(Goal)).
myfreeze(X, Goal) :- put_atts(Fresh, frozen(Goal)), Fresh = X. ~~~~~ @endicode
Assuming that this code lives in file myfreeze.yap, we would use it via:
@icode | ?- use_module(myfreeze). | ?- myfreeze(X,print(bound(x,X))), X=2.
bound(x,2) % side effect X = 2 % bindings @endicode
The two solvers even work together:
@icode | ?- myfreeze(X,print(bound(x,X))), domain(X,[1,2,3]), domain(Y,[2,10]), X=Y.
bound(x,2) % side effect X = 2, % bindings Y = 2 @endicode
The two example solvers interact via bindings to shared attributed variables only. More complicated interactions are likely to be found in more sophisticated solvers. The corresponding @ref verify_attributes_51 @"verify_attributes/3" predicates would typically refer to the attributes from other known solvers/modules via the module prefix in Module: @ref get_atts_50 @"get_atts/2".