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

  • Library usage:
    :- use_package(argnames). or :- module(...,...,[argnames]).
  • Exports:
  • New operators defined:
    $/2 [150,xfx], =>/2 [950,xfx], argnames/1 [1150,fx].
  • New declarations defined:
    argnames/1.

Documentation on new declarations

DECLARATION

Usage: :- argnames ArgNamedPredSpec.

  • Description: 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 ocurrence 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

Usage: $~(Term,Replacement,NewTerm)

  • Description: 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).


Known bugs and planned improvements

  • It would be nice to add a mechanism to portray terms with named arguments in a special (user definable) way.
  • The predicate $~ still does not support runtime argnames.