Constants in SWI-Prolog?

I’m using: SWI-Prolog version 8.1.9.

Is there an equivalent to a constant label in Prolog? I’m using arg/3 to pull out “fields” from a frequently used Prolog term I build in my app. If the structure of that term changes, I’d like to be able to update a constant label that identifies that field’s argument position in the term, instead of finding all instances of arg/3 in my code that access that term and field and then updating the integer value in for the argument number I find in each place.

Is there a constant label idiom in SWI-Prolog?

Pseudo-code:

:- const ARG_NUM_CAT_NAME = 2 % 1-based arg number

get_cat_name(Cat, CatName) :-
    !,
    arg(CAT_NAME_FLD_NUM, Cat, CatName).
?- get_cat_name(cat_details(field_1(cat), field_2(fluffy)), CatName), write(CatName).
fluffy
true.
1 Like

Not clear how that frequently used term will be represented and accessed. Will it be a term found in the Prolog database? A term being passed as argument to predicates? Can there be multiple instances of the term at any given time?

Take a look at SWI-Prolog dicts:
https://www.swi-prolog.org/pldoc/man?section=bidicts
Dicts provide O(log n) access to named arguments and some handy operations.

An alternative is to use Logtalk parametric objects and parameter variables (which are logical variables providing O(1) access). The parameter variable names will work as your “constant labels”.

Continuing your cat example, assume your frequently used term is cat/3 but you want to (1) access its arguments independently of position and (2) be able to add/remove arguments with minimal changes to your code.

% cat(Name, Age, FoodPreferences)
cat(garfield, 12, lasagna).
cat(fluffy, 1, milk).
...

We can easily define a parametric object that uses as identifier the compound term and use parameter variables (written as VarName) to abstract position:

:- object(cat(_Name_, _Age_, _Food_)).

    :- public(name/1).
    name(_Name_).

    :- public(age/1).
    age(_Age_).

    :- public(food/1).
    food(_Food_).

:- end_object. 

Let’s print all cat names in the cat (plain Prolog) database:

?- {cat(_,_,_)}::name(Name), writeln(Name), fail; true.
garfield
fluffy
true.

This is also a good solution if you want to do more than simply access a named argument. E.g. define a predicate that makes some computations over the field name but keep them together with the access predicates.

Now assume that we also want to represent each cat mortal enemy. E.g.

% cat(Name, Age, FoodPreferences, Enemy)
cat(garfield, 12, lasagna, odie).

We can keep all existing code working with the new representation by defining:

:- object(cat(_Name_, _Age_, _Food_, _Enemy_)).

    :- public(name/1).
    name(_Name_).

    :- public(age/1).
    age(_Age_).

    :- public(food/1).
    food(_Food_).

    :- public(enemy/1).
    enemy(_Enemy_).

:- end_object.


:- object(cat(_Name_, _Age_, _Food_),
	extends(cat(_Name_, _Age_, _Food_, _))).

:- end_object.

Old queries that used cat/3 will continue to work as-is and new ones can start using the updated cat/4 representation. Of course, the cat terms don’t need to exist as clauses in the database. They can simply be a term being passed around that you query. Re-using your example predicate:

get_cat_name(Cat, CatName) :-
    !,
    Cat::name(CatName).

But, if that’s the case, dicts are a good solution. You would simply use name as the argument name.

2 Likes

And there is library(record) for O(1) access. It is a bit verbose though. Dict access is indeed O(log(N)), but the constant factor is pretty low. Up to hundreds of keys you are generally pretty ok. They come also very handy if the set of keys is not known in advance.

2 Likes