/************************************************************************* * * * YAP Prolog * * * * Yap Prolog was developed at NCCUP - Universidade do Porto * * * * Copyright L.Damas, V.S.Costa and Universidade do Porto 1985-1997 * * * ************************************************************************** * * * File: atts.yap * * Last rev: 8/2/88 * * mods: * * comments: attribute support for Prolog * * * *************************************************************************/ :- module(attributes, [op(1150, fx, attribute)]). %% @{ /** @defgroup Old_Style_Attribute_Declarations SICStus Prolog style Attribute Declarations @ingroup Attributed_Variables Old style attribute declarations are activated through loading the library atts . The command ~~~~~ | ?- use_module(library(atts)). ~~~~~ enables this form of use of attributed variables. The package provides the following functionality: + Each attribute must be declared first. Attributes are described by a functor and are declared per module. Each Prolog module declares its own sets of attributes. Different modules may have different functors with the same module. + The built-in 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 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 verify_attributes/3 before trying to bind an attributed variable. Unification will resume after this call. + The user-defined predicate attribute_goal/2 converts from an attribute to a goal. + The user-defined predicate project_attributes/2 is used from a set of variables into a set of constraints or goals. One application of 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. */ %% @} %% @{ /** @defgroup Attribute_Declarations Attribute Declarations @ingroup Old_Style_Attribute_Declarations 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 with the following declaration: ~~~~~ :- attribute AttributeSpec, ..., AttributeSpec. ~~~~~ 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: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-predicate predicate 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 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 project_attributes/2 is responsible for implementing this projection. The following two examples example is taken from the SICStus Prolog manual. It 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 project_attributes/2. Projecting finite domain constraints happens to be difficult. ~~~~~ :- module(domain, [domain/2]). :- use_module(library(atts)). :- use_module(library(ordsets), [ ord_intersection/3, ord_intersect/2, list_to_ord_set/2 ]). :- attribute 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 % verify_attributes/3 ). ~~~~~ Note that the _implied binding_ `Other=El` was deferred until after the completion of `verify_attribute/3`. Otherwise, there might be a danger of recursively invoking `verify_attribute/3`, which might bind `Var`, which is not allowed inside the scope of `verify_attribute/3`. Deferring unifications into the third argument of `verify_attribute/3` effectively serializes the calls to `verify_attribute/3`. Assuming that the code resides in the file domain.yap, we can use it via: ~~~~~ | ?- use_module(domain). ~~~~~ Let's test it: ~~~~~ | ?- 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 ~~~~~ To demonstrate the use of the _Goals_ argument of verify_attributes/3, we give an implementation of freeze/2. We have to name it `myfreeze/2` in order to avoid a name clash with the built-in predicate of the same name. ~~~~~ :- module(myfreeze, [myfreeze/2]). :- use_module(library(atts)). :- attribute 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. ~~~~~ Assuming that this code lives in file myfreeze.yap, we would use it via: ~~~~~ | ?- use_module(myfreeze). | ?- myfreeze(X,print(bound(x,X))), X=2. bound(x,2) % side effect X = 2 % bindings ~~~~~ The two solvers even work together: ~~~~~ | ?- 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 ~~~~~ 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 verify_attributes/3 predicates would typically refer to the attributes from other known solvers/modules via the module prefix in ` _Module_:get_atts/2`. */ :- use_module(library(lists), [member/2]). :- multifile user:goal_expansion/3. :- multifile user:term_expansion/2. :- dynamic existing_attribute/4. :- dynamic modules_with_attributes/1. :- dynamic attributed_module/3. modules_with_attributes([]). % % defining a new attribute is just a question of establishing a % Functor, Mod -> INT mappings % new_attribute(V) :- var(V), !, throw(error(instantiation_error,attribute(V))). new_attribute((At1,At2)) :- new_attribute(At1), new_attribute(At2). new_attribute(Na/Ar) :- source_module(Mod), functor(S,Na,Ar), existing_attribute(S,Mod,_,_) , !. new_attribute(Na/Ar) :- source_module(Mod), functor(S,Na,Ar), store_new_module(Mod,Ar,Position), assertz(existing_attribute(S,Mod,Ar,Position)). store_new_module(Mod,Ar,ArgPosition) :- ( retract(attributed_module(Mod,Position,_)) -> true ; retract(modules_with_attributes(Mods)), assert(modules_with_attributes([Mod|Mods])), Position = 2 ), ArgPosition is Position+1, ( Ar == 0 -> NOfAtts is Position+1 ; NOfAtts is Position+Ar), functor(AccessTerm,Mod,NOfAtts), assertz(attributed_module(Mod,NOfAtts,AccessTerm)). :- user_defined_directive(attribute(G), attributes:new_attribute(G)). /** @pred _Module_:get_atts( _-Var_, _?ListOfAttributes_) Unify the list _?ListOfAttributes_ with the attributes for the unbound variable _Var_. Each member of the list must be a bound term of the form `+( _Attribute_)`, `-( _Attribute_)` (the kbd prefix may be dropped). The meaning of + and - is: + +( _Attribute_) Unifies _Attribute_ with a corresponding attribute associated with _Var_, fails otherwise. + -( _Attribute_) Succeeds if a corresponding attribute is not associated with _Var_. The arguments of _Attribute_ are ignored. */ user:goal_expansion(get_atts(Var,AccessSpec), Mod, Goal) :- expand_get_attributes(AccessSpec,Mod,Var,Goal). /** @pred _Module_:put_atts( _-Var_, _?ListOfAttributes_) Associate with or remove attributes from a variable _Var_. The attributes are given in _?ListOfAttributes_, and the action depends on how they are prefixed: + +( _Attribute_) Associate _Var_ with _Attribute_. A previous value for the attribute is simply replace (like with `set_mutable/2`). + -( _Attribute_) Remove the attribute with the same name. If no such attribute existed, simply succeed. */ user:goal_expansion(put_atts(Var,AccessSpec), Mod, Goal) :- expand_put_attributes(AccessSpec, Mod, Var, Goal). expand_get_attributes(V,_,_,_) :- var(V), !, fail. expand_get_attributes([],_,_,true) :- !. expand_get_attributes([-G1],Mod,V,attributes:free_att(V,Mod,Pos)) :- existing_attribute(G1,Mod,_,Pos), !. expand_get_attributes([+G1],Mod,V,attributes:get_att(V,Mod,Pos,A)) :- existing_attribute(G1,Mod,1,Pos), !, arg(1,G1,A). expand_get_attributes([G1],Mod,V,attributes:get_att(V,Mod,Pos,A)) :- existing_attribute(G1,Mod,1,Pos), !, arg(1,G1,A). expand_get_attributes(Atts,Mod,Var,attributes:get_module_atts(Var,AccessTerm)) :- Atts = [_|_], !, attributed_module(Mod,NOfAtts,AccessTerm), void_term(Void), cvt_atts(Atts,Mod,Void,LAtts), sort(LAtts,SortedLAtts), free_term(Free), build_att_term(1,NOfAtts,SortedLAtts,Free,AccessTerm). expand_get_attributes(Att,Mod,Var,Goal) :- expand_get_attributes([Att],Mod,Var,Goal). build_att_term(NOfAtts,NOfAtts,[],_,_) :- !. build_att_term(I0,NOfAtts,[I-Info|SortedLAtts],Void,AccessTerm) :- I is I0+1, !, copy_att_args(Info,I0,NI,AccessTerm), build_att_term(NI,NOfAtts,SortedLAtts,Void,AccessTerm). build_att_term(I0,NOfAtts,SortedLAtts,Void,AccessTerm) :- I is I0+1, arg(I,AccessTerm,Void), build_att_term(I,NOfAtts,SortedLAtts,Void,AccessTerm). cvt_atts(V,_,_,_) :- var(V), !, fail. cvt_atts([],_,_,[]). cvt_atts([V|_],_,_,_) :- var(V), !, fail. cvt_atts([+Att|Atts],Mod,Void,[Pos-LAtts|Read]) :- !, existing_attribute(Att,Mod,_,Pos), (atom(Att) -> LAtts = [_] ; Att=..[_|LAtts]), cvt_atts(Atts,Mod,Void,Read). cvt_atts([-Att|Atts],Mod,Void,[Pos-LVoids|Read]) :- !, existing_attribute(Att,Mod,_,Pos), ( atom(Att) -> LVoids = [Void] ; Att =..[_|LAtts], void_vars(LAtts,Void,LVoids) ), cvt_atts(Atts,Mod,Void,Read). cvt_atts([Att|Atts],Mod,Void,[Pos-LAtts|Read]) :- !, existing_attribute(Att,Mod,_,Pos), (atom(Att) -> LAtts = [_] ; Att=..[_|LAtts]), cvt_atts(Atts,Mod,Void,Read). copy_att_args([],I,I,_). copy_att_args([V|Info],I,NI,AccessTerm) :- I1 is I+1, arg(I1,AccessTerm,V), copy_att_args(Info,I1,NI,AccessTerm). void_vars([],_,[]). void_vars([_|LAtts],Void,[Void|LVoids]) :- void_vars(LAtts,Void,LVoids). expand_put_attributes(V,_,_,_) :- var(V), !, fail. expand_put_attributes([-G1],Mod,V,attributes:rm_att(V,Mod,NOfAtts,Pos)) :- existing_attribute(G1,Mod,_,Pos), !, attributed_module(Mod,NOfAtts,_). expand_put_attributes([+G1],Mod,V,attributes:put_att(V,Mod,NOfAtts,Pos,A)) :- existing_attribute(G1,Mod,1,Pos), !, attributed_module(Mod,NOfAtts,_), arg(1,G1,A). expand_put_attributes([G1],Mod,V,attributes:put_att(V,Mod,NOfAtts,Pos,A)) :- existing_attribute(G1,Mod,1,Pos), !, attributed_module(Mod,NOfAtts,_), arg(1,G1,A). expand_put_attributes(Atts,Mod,Var,attributes:put_module_atts(Var,AccessTerm)) :- Atts = [_|_], !, attributed_module(Mod,NOfAtts,AccessTerm), void_term(Void), cvt_atts(Atts,Mod,Void,LAtts), sort(LAtts,SortedLAtts), free_term(Free), build_att_term(1,NOfAtts,SortedLAtts,Free,AccessTerm). expand_put_attributes(Att,Mod,Var,Goal) :- expand_put_attributes([Att],Mod,Var,Goal). woken_att_do(AttVar, Binding, NGoals, DoNotBind) :- modules_with_attributes(AttVar,Mods0), modules_with_attributes(Mods), find_used(Mods,Mods0,[],ModsI), do_verify_attributes(ModsI, AttVar, Binding, Goals), process_goals(Goals, NGoals, DoNotBind). % dirty trick to be able to unbind a variable that has been constrained. process_goals([], [], _). process_goals((M:do_not_bind_variable(Gs)).Goals, (M:Gs).NGoals, true) :- !, process_goals(Goals, NGoals, _). process_goals(G.Goals, G.NGoals, Do) :- process_goals(Goals, NGoals, Do). find_used([],_,L,L). find_used([M|Mods],Mods0,L0,Lf) :- member(M,Mods0), !, find_used(Mods,Mods0,[M|L0],Lf). find_used([_|Mods],Mods0,L0,Lf) :- find_used(Mods,Mods0,L0,Lf). /** @pred _Module_:verify_attributes( _-Var_, _+Value_, _-Goals_) The predicate is called when trying to unify the attributed variable _Var_ with the Prolog term _Value_. Note that _Value_ may be itself an attributed variable, or may contain attributed variables. The goal verify_attributes/3 is actually called before _Var_ is unified with _Value_. It is up to the user to define which actions may be performed by verify_attributes/3 but the procedure is expected to return in _Goals_ a list of goals to be called after _Var_ is unified with _Value_. If verify_attributes/3 fails, the unification will fail. Notice that the verify_attributes/3 may be called even if _Var_< has no attributes in module Module. In this case the routine should simply succeed with _Goals_ unified with the empty list. */ do_verify_attributes([], _, _, []). do_verify_attributes([Mod|Mods], AttVar, Binding, [Mod:Goal|Goals]) :- current_predicate(verify_attributes,Mod:verify_attributes(_,_,_)), !, Mod:verify_attributes(AttVar, Binding, Goal), do_verify_attributes(Mods, AttVar, Binding, Goals). do_verify_attributes([_|Mods], AttVar, Binding, Goals) :- do_verify_attributes(Mods, AttVar, Binding, Goals). /** @} */