Terms with named arguments -records/feature terms

Author(s): Daniel Cabeza, Manuel Hermenegildo, Jose F. Morales.

This library package provides syntax which allows accessing term arguments by name (these terms are sometimes also referred to as records, and are also similar to feature terms [AKPS92]).

Usage and interface

Documentation on new declarations

DECLARATIONargnames/1

Usage::- argnames(ArgNamedPredSpec).

An argnames/1 declaration assigns names to the argument positions of terms (or literal/goals) which use a certain functor/arity. This allows referring to these arguments by their name rather than by their argument position. Sometimes, argument names may be clearer and easier to remember than argument positions, specially for predicates with many arguments. Also, in some cases this may allow adding arguments to certain predicates without having to change the code that uses them. These terms with named arguments are sometimes also referred to as records, and are also similar to feature terms [AKPS92]. For example, in order to write a program for the zebra puzzle we might declare:

:- use_package([argnames]).
:- argnames house(color, nation, pet, drink, car).

which first includes the package and then assigns a name to each of the arguments of any term (or literal/goal) with house/5 as the main functor.

For convenience the package extends the built-in data/1 declaration so that names to arguments can be asigned as with the argnames/1 declaration, as for example:

:- data product(id, description, brand, quantity).

Once an argnames/1 is given, it is possible to use the names to refer to the arguments of any term (or literal/goal) which has the same main functor as that of the term which appears in the argnames/1 declaration. This is done by first writing the functor name, then the infix operator $, and then, between curly brackets, zero, one, or more pairs argument-name=>argument-value, separated by commas (i.e., the infix operator => is used between the name and the value). Again, argument names must be atomic. Argument values can be any term. Arguments which are not specified are assumed to have a value of ``_'' (i.e., they are left unconstrained).

Thus, after the declaration for house/5 in the example above, any occurrence in that code of, for example, house${nation=>Owns_zebra,pet=>zebra} is exactly equivalent to house(_,Owns_zebra,zebra,_,_). Also, house${} is equivalent to house(_,_,_,_,_). The actual zebra puzzle specification might include a clause such as:

zebra(Owns_zebra, Drinks_water, Street) :-
    Street = [house${},house${},house${},house${},house${}],
    member(house${nation=>Owns_zebra,pet=>zebra}, Street),
    member(house${nation=>Drinks_water,drink=>water}, Street),
    member(house${drink=>coffee,color=>green}, Street),
    left_right(house${color=>ivory}, house${color=>green}, Street),
    member(house${car=>porsche,pet=>snails}, Street),
    ...
Another syntax supported, useful mainly in declarations to avoid specifying the arity, is house${/}, which is equivalent in our example to house/5 (but for data declarations there is a special syntax as we have seen).

Any number of argnames/1 declarations can appear in a file, one for each functor whose arguments are to be accessed by name. As with other packages, argument name declarations are local to the file in which they appear. The argnames/1 declarations affect only program text which appears after the declaration. It is easy to make a set of declarations affect several files for example by putting such declarations in a sepatate file which is included by all such files.

An argnames/1 declaration does not change in any way the internal representation of the associated terms and does not affect run-time efficiency. It is simply syntactic sugar.

Runtime support

It is possible to write pairs with unbound argument names. In that case, runtime information is emitted to resolve the argument name at execution time.

    Documentation on exports

    PREDICATE$~/3

    Usage:$~(Term,Replacement,NewTerm)

    NewTerm is as Term but with the arguments specified in Replacement changed (they need to be in argnames syntax). The predicate is in fact virtual, since it is translated by the package to a pair of unifications. For example, given the declaration :- argnames house(color, nation, pet, drink, car), the goal

    $~(House, house${car => seat, pet => mouse}, NewHouse)
    would be compiled to the unifications
    House = house(C,N,_,D,_), NewHouse = house(C,N,mouse,D,seat).

      Other information

      Two simple examples of the use of the argnames library package follow.

      Using argument names in a toy database

      :- module(simple_db,_,[argnames,assertions,regtypes]).
      :- use_module(library(aggregates)).
      
      :- doc(title,"A simple database application using argument names").
      
      :- data
      product( id,    description,    brand,          quantity        ).
      %       ----------------------------------------------------------
      product(  1,    "Keyboard",     "Logitech",     6               ).
      product(  2,    "Mouse",        "Logitech",     5               ).
      product(  3,    "Monitor",      "Philips",      3               ).
      product(  4,    "Laptop",       "Dell",         4               ).
      % (${/} must go after argnames)
      :- pred product${/}
         ::    int    * string        * string        * int.
      
      % Compute the stock of products from a given brand.
      % Note call to findall is equivalent to: findall(Q,product(_,_,Brand,Q),L).
      
      brand_stock(Brand,Stock) :-
          findall(Q,product${brand=>Brand,quantity=>Q},L),
          sumlist(L,Stock).
      
      sumlist([],0).
      sumlist([X|T],S) :- 
          sumlist(T,S1),
          S is X + S1.
      
      

      Complete code for the zebra example

      :- module(_,zebra/3,[argnames]).
      
      /*     There are five consecutive houses, each of a different
      color and inhabited by men of different nationalities. They each
      own a different pet, have a different favorite drink, and drive a
      different car.
      
      1.   The Englishman lives in the red house.
      2.   The Spaniard owns the dog.
      3.   Coffee is drunk in the green house.
      4.   The Ukrainian drinks tea.
      5.   The green house is immediately to the right of the ivory
           house.
      6.   The Porsche driver owns snails.
      7.   The Masserati is driven by the man who lives in the yellow
           house.
      8.   Milk is drunk in the middle house.
      9.   The Norwegian lives in the first house on the left.
      10.  The man who drives a Saab lives in the house next to the man
           with the fox.
      11.  The Masserati is driven by the man in the house next to the
           house where the horse is kept.
      12.  The Honda driver drinks orange juice.
      13.  The Japanese drives a Jaguar.
      14.  The Norwegian lives next to the blue house.
      
      The problem is: Who owns the Zebra?  Who drinks water?
      */
      
      :- argnames house(color, nation, pet, drink, car).
      
      zebra(Owns_zebra, Drinks_water, Street) :-
          Street = [house${},house${},house${},house${},house${}],
          member(house${nation => Owns_zebra, pet => zebra}, Street),
          member(house${nation => Drinks_water, drink => water}, Street),
          member(house${nation => englishman, color => red}, Street),
          member(house${nation => spaniard, pet => dog}, Street),
          member(house${drink => coffee, color => green}, Street),
          member(house${nation => ukrainian, drink => tea}, Street),
          left_right(house${color => ivory}, house${color => green}, Street),
          member(house${car => porsche, pet => snails}, Street),
          member(house${car => masserati, color => yellow}, Street),
          Street = [_, _, house${drink => milk}, _, _],
          Street = [house${nation => norwegian}|_],
          next_to(house${car => saab}, house${pet => fox}, Street),
          next_to(house${car => masserati}, house${pet => horse}, Street),
          member(house${car => honda, drink => orange_juice}, Street),
          member(house${nation => japanese, car => jaguar}, Street),
          next_to(house${nation => norwegian}, house${color => blue}, Street).
      
      
      member(X,[X|_]).
      member(X,[_|Y]) :- member(X,Y).
      
      left_right(L,R,[L,R|_]).
      left_right(L,R,[_|T]) :- left_right(L,R,T).
      
      next_to(X,Y,L) :- left_right(X,Y,L).
      next_to(X,Y,L) :- left_right(Y,X,L).