Examples of Using Attributed Variables

## 16.9 Examples of Using Attributed Variables

- Variables with Enumerated Domains

### 16.9.1 Variables with Enumerated Domains

As an example, let us implement variables of enumerable types using attributes. We choose to represent these variable as attributed variables whose attribute is a enum/1 structure with a list holding the values the variable may take, e.g.

>     X{enum([a,b,c])}

We have to specify now what should happen when such a variable is bound. This is done by writing a handler for the unify operation. The predicate unify_enum/2 defined below is this handler. Its first argument is the value that the attributed variable has been bound to, the second is the attribute that the bound attributed variable had (keep in mind that the system has already bound the attributed variable to the new value). We distinguish two cases:
First, the attributed variable has been bound to another attributed variable (1st clause of unify_enum/2). In this case, we form the intersection between the two lists of admissible values. If it is empty, we fail. If it contains exactly one value, we can instantiate the remaining attributed variable with this value. Otherwise, we bind it to a new attributed variable whose attribute represents the remaining admissible values.
Second, when the attributed variable has been bound to a non-variable, the task that remains for the handler is merely to check if this binding was admissible (2nd clause of unify_enum/2).

>     [eclipse 2]: module(enum).
>     warning: creating a new module in module(enum)
>     [enum 3]: [user].
>     :- meta_attribute(enum, [unify:unify_enum/2, print:print_enum/2]).
>     :- import setarg/3 from sepia_kernel.

>     % unify_enum(+Term, Attribute)
>     unify_enum(_, Attr) :-
>         /*** ANY + VAR ***/
>         var(Attr).                  % Ignore if no attribute for this extension
>     unify_enum(Term, Attr) :-
>         compound(Attr),
>         unify_term_enum(Term, Attr).

>     unify_term_enum(Value, enum(ListY)) :-
>         nonvar(Value),               % The attributed variable was instantiated
>         /*** NONVAR + META ***/
>         memberchk(Value, ListY).
>     unify_term_enum(Y{AttrY}, AttrX) :-
>         -?->
>         unify_enum_enum(Y, AttrX, AttrY).

>     unify_enum_enum(_, AttrX, AttrY) :-
>         var(AttrY),                         % no attribute for this extension
>         /*** VAR + META ***/
>         AttrX = AttrY.                      % share the attribute
>     unify_enum_enum(Y, enum(ListX), AttrY) :-
>         nonvar(AttrY),
>         /*** META + META ***/
>         AttrY = enum(ListY),
>          intersection(ListX, ListY, ListXY),
>          ( ListXY = [Val] ->
>                 Y = Val
>          ;
>                 ListXY \= [],
>                 setarg(1, AttrY, ListXY)
>          ).

>     print_enum(enum(List), Attr) :-
>         -?->
>         Attr = List.
>      user       compiled traceable 1188 bytes in 0.03 seconds

>     yes.
>     [enum 4]: A{enum([yellow, blue, white, green])}
>                     = B{enum([orange, blue, red, yellow])}.

>     A = B = A{[blue, yellow]}
>     yes.
>     [enum 5]: A{enum([yellow, blue, white, green])}
>                     = B{enum([orange, blue, red, black])}.

>     A = B = blue
>     yes.
>     [enum 6]: A{enum([yellow, blue, white, green])} = white.

>     A = white
>     yes.
>     [enum 7]: A{enum([yellow, blue, white, green])} = red.

>     no (more) solution.

Some further remarks on this code: The second clause of unify_term_enum/2 is a matching clause, as indicated by the −?−> guard. A matching clause is the only way to decompose an attributed variable. Note that this clause matches only calls that have an attributed variable with nonempty enum attribute on the first argument position.