Previous Up Next

16.7  Attributed Variable Handlers

An attributed variable is a variable with some additional information which is ignored by ordinary object level system predicates. Meta level operations on attributed variables are handled by extensions which know the contents of their attributes and can specify the outcome of each operation. This mechanism is implemented using attributed variable handlers, which are user-defined predicates invoked whenever an attributed variable occurs in one of the predefined operations. The handlers are specified in the attribute declaration meta_attribute(NameHandlerList), the second argument is a list of handlers in the form

[unify:UnifyHandler, test_unify:TUHandler, ...]

Handlers for operations which are not specified or those that are true/0 are ignored and never invoked. If Name is an existing extension, the specified handlers replace the current ones.

Whenever one of the specified operations detects an attributed variable, it will invoke all handlers that were declared for it and each of them receives either the whole attributed variable or its particular attribute as argument. The system does not check if the attribute that corresponds to a given handler is instantiated or not; this means that the handler must check itself if the attributed variable contains any attribute information or not. For instance, if an attributed variable X{a:_, b:_, c:f(a)} is unified with the attributed variable Y{a:_, b:_, c:f(b)}, the handlers for the attributes a and b should treat this as binding of two plain variables because their attributes were not involved. Only the handler for c has any work to do here. The library suspend can be used as a template for writing attributed variable handlers.

The following operations invoke attributed variable handlers:

unify:
the usual unification. The handler procedure is
unify_handler(+Term, ?Attribute [, ?SuspAttr])
The first argument is the term that was unified with the attributed variable, it is either a non-variable or another attributed variable. The second argument is the contents of the attribute slot corresponding to the extension. Note that, at this point in execution, the orginal attributed variable no longer exists, because it has already been bound to Term. The optional third argument is the suspend-attribute of the former variable; it may be needed to wake the variable’s ’constrained’ suspension list.

The handler’s job is to determine whether the binding is allowed with respect to the attribute. This could for example involve checking whether the bound term is in a domain described by the attribute. For variable-variable bindings, typically the remaining attribute must be updated to reflect the intersection of the two individual attributes. In case of success, suspension lists inside the attributes may need to be scheduled for waking.

If an attributed variable is unified with a standard variable, the variable is bound to the attributed variable and no handlers are invoked. If an attributed variable is unified with another attributed variable or a non-variable, the attributed variable is bound (like a standard variable) to the other term and all handlers for the unify operation are invoked. Note that several attributed variable bindings can occur simultaneously, e.g. during a head unification or during the unification of two compound terms. The handlers are only invoked at certain trigger points (usually before the next regular predicate call). Woken goals will start executing once all unify-handlers are done.

test_unify:
a unifiability test which is not supposed to trigger constraints propagation. It is used by the not_unify/2 predicate. The handler procedure is
test_unify_handler(+Term, ?Attribute)
where the arguments are the same as for the unify handler. The handler’s job is to determine whether Attribute allows unification with Term (not considering effects of woken goals). During the execution of the handler, the attributed variable may be bound to Term, however when all attribute handlers succeed, all bindings are undone again, and no waking occurs.
compare_instances:
computation of instance, subsumption and variance relationship, as performed by the built-ins compare_instances/3, instance/2 and variant/2. The handler procedure is
instance_handler(-Res, ?TermL, ?TermR)
and its arguments are similar to the ones of the compare_instances/3 predicate. The handler is invoked with one or both of TermL and TermR being attributed variables. The task of the handler is to examine the two terms, and compute their instance relationship with respect to the extension attribute in question. The handler must bind Res to = iff the terms are variants, < iff TermL is a proper instance of TermR, or > iff TermR is a proper instance of TermL) with respect to the attribute under consideration. If the terms are not unifiable with respect to this attribute, the handler must fail.

Even though one of TermL and TermR is guaranteed to be an attributed variable, they might not have the particular attribute that the handler is concerned with. The handler must therefore be written to correctly deal with all combinations of an attributed (but potentially uninstantiated attribute) variable with any other term.

copy_term:
the handler is invoked by either copy_term/2 or copy_term_vars/3. The handler procedure is
copy_handler(?AttrVar, ?Copy)
AttrVar is the attributed variable encountered in the copied term, Copy is its corresponding variable in the copy. All extension handlers receive the same arguments. This means that if the attributed variable should be copied as an attributed variable, the handler must check if Copy is still a free variable or if it was already bound to an attributed variable by a previous handler.
suspensions:
this handler is invoked by the suspensions/2 predicate to collect all the suspension lists inside the attribute. The handler call pattern is
suspensions_handler(?AttrVar, -ListOfSuspLists, -Tail)
AttrVar is an attributed variable. The handler should bind ListOfSuspLists to a list containing all the attribute’s suspension lists and ending with Tail.
delayed_goals_number:
handler is invoked by the delayed_goals_number/2 predicate. The handler call pattern is
delayed_goals_number_handler(?AttrVar, -Number)
AttrVar is the attributed variable encountered in the term, Number is the number of delayed goals occurring in this attribute. Its main purpose is for the first-fail selection predicates, i.e., it should return the number of constraints imposed on the variable.
get_bounds:
This handler is used by the predicate get_var_bounds/3 to retrieve information about the lower and upper bound of a numeric variable. The handler should therefore only be defined if the attribute contains that kind of information. The handler call pattern is
get_bounds_handler(?AttrVar, -Lwb, -Upb)
The handler is only invoked if the variable has the corresponding (non-empty) attribute. The handler should bind Lwb and Upb to numbers (any numeric type) reflecting the attribute’s information about lower and upper bound of the variable, respectively. If different attributes return different bounds information, get_var_bounds/3 will return the intersection of these bounds. This can be empty (Lwb > Upb).
set_bounds:
This handler is used by the predicate set_var_bounds/3 to distribute information about the lower and upper bound of a numeric variable to all its existing attributes. The handler should therefore only be defined if the attribute can incorporate this kind of information. The handler call pattern is
set_bounds_handler(?AttrVar, +Lwb, +Upb)
The handler is only invoked if the variable has the corresponding (non-empty) attribute. Lwb and Upb are the numbers that were passed to set_var_bounds/3, and the handler is expected to update its own bounds representation accordingly.
print:
attribute printing in write/1,2, writeln/1,2, printf/2,3 when the m option is specified. The handler procedure is
print_handler(?AttrVar, -PrintAttr)
AttrVar is the attributed variable being printed, PrintAttr is the term which will be printed as a value for this attribute, prefixed by the attribute name. If no handler is specified for an attribute, or the print handler fails, the attribute will not be printed.

The following handlers are still supported for compatibility, but their use is not recommened:

pre_unify:
this is another handler which can be invoked on normal unification, but it is called before the unification itself occurs. The handler procedure is
pre_unify_handler(?AttrVar, +Term [, -Goals])
The first argument is the attributed variable to be unfied, the second argument is the term it is going to be unified with. The optional third argument can be used to return goals that will be called after all pre-unify handlers for this variable have finished, and the variable has been bound. The handlers itself should not bind any variables. If multiple attributed variables were bound in a single unification, all these bindings are first undone, then the handlers are called and the variables re-bound one by one. This handler is provided for compatibility with SICStus Prolog and its use is not recommended. It can be used together with a unify handler, which is called afterwards.
delayed_goals:
this handler is superseded by the suspensions-handler, which should be preferred. If there is no suspensions- handler, this handler is invoked by the obsolete delayed_goals/2 predicate. The handler procedure is
delayed_goals_handler(?AttrVar, ?GoalList, -GoalCont)
AttrVar is the attributed variable encountered in the term, GoalList is an open-ended list of all delayed goals in this attribute and GoalCont is the tail of this list.

16.7.1  Printing Attributed Variables

The different output predicates treat attributed variables differently. The write/1 predicate prints the attributes using the print-handlers, while writeq/1 prints the whole attribute, so that the attributed variable can be read back. The printf/2 predicate has two options to be combined with the w format: M forces the whole attributed variable to be printed together with all its attributes in the standard format, so that it can be read back in. With the m option the attributed variable is printed using the handlers defined for the print operation. If there is only one handled attribute, the attributed variable is printed as

X{Attr}

where Attr is the value obtained from the handler. If there are several handled attributes, all attributes are qualified like in

X{a:A, b:B, c:C}.

A simple print handler can just return the attribute literally, like

print_attr(_{Attr}, PrintAttr) ?- PrintAttr=Attr.

An attributed variable X{m:a} with print handler print_attr/2 for the m-attribute, can thus be printed in different ways, e.g., 1

printf("%w", [X{m:a}])   or write(X{m:a}):   X
printf("%vMw", [X{m:a}]) or writeq(X{m:a}):  _g246{suspend : _g242, m : a}
printf("%mw", [X{m:a}]):                     X{a}
printf("%Mw", [X{m:a}]):                     X{suspend : _g251, m : a}
printf("%Vmw", [X{m:a}]):                    X_g252{a}

Write macros for attributed variables are not allowed because one extension alone should not decide whether the other attributes will be printed or not.


1
The attribute suspend is always present and defined by system coroutining.

Previous Up Next