Confusion with C++ interface and Prolog lists

The examples do a good job of explaining PREDICATE and unifying atoms and integers within those predicates. I am struggling to understand how to unify new Prolog lists within this context and there
are not a lot of examples using PlTerm_tail.

Suppose I want to write a C++ predicate that returns successive squares starting at integer N1 for N2 values. This would look something like
?- csquares(5,3,X).
X = [25, 36, 49].

What is the proper use of PLTerm_tail and unification strategy ?

1 Like

Iā€™m having the same problem, apparently the documentation lacks an explanation about it.
This post is a bit old, I donā€™t know wheter should I make a new one or not. Did you come up with a solution in this year and 2 months? I could use the PlTerm_tail as it was described in SWI-cpp.h by which I mean I was able to use append and close without generating any errors. However I couldnā€™t use the next() method as it needs a PlTerm&, which I cannot have using the SWI-cpp2.h library. That aside, the prolog engine doesnā€™t seem to recognize the PlTerm_tail object, either that or Iā€™m using it wrong. For example I define it like this:

PlTermv av(2);
PlTerm_tail list(av[0]);
for(int i = 0; i<5; i++) {
    PlTerm t(PlAtom(someString[i].c_str()));
    if (l.append(t)) cout << "element " << i << " inserted, it's " << t.as_string() << "\n";
}
l.close();
PlQuery q("myRule", av);
cout << av[1].as_string() << " before next_solution()\n";
q.next_solution();
cout << av[1].as_string() << " after next_solution()\n";

And in both cases I get _ when I should get a result from myRule/2.

Mr @peter.ludemann maybe you can help us? Iā€™m sure I made some mistakes here

Do these examples from test_cpp.cpp help? (And you can see how theyā€™re called in test_cpp.pl) These (and other examples) are in GitHub - SWI-Prolog/packages-cpp: The SWI-Prolog C++ interface

PREDICATE(cappend, 3)
{ PlTerm_tail l1(A1);
  PlTerm_tail l3(A3);
  PlTerm_var e;

  while( l1.next(e) )
    PlCheckFail(l3.append(e));

  return A2.unify_term(l3);
}
PREDICATE(square_roots, 2)
{ int end = A1.as_int();
  PlTerm_tail list(A2);

  for(int i=0; i<=end; i++)
    PlCheckFail(list.append(PlTerm_float(sqrt(double(i)))));

  return list.close();
}
1 Like

Thank you for your answer sir! Apparently one of the problem I was having was the use of PlTerm_var in order to go through the list.

Although, the examples provided within the repository arenā€™t all that helpful in my use case. I also mean to clarify my issue since in the previous post I was kinda hasty and Iā€™m sorry for that.

What Iā€™m trying to do is to use my prolog knowledge base within a cpp environment. To be more precise, I want to run the prolog engine, provide a query to it and be able to get and manage the answer. I did, however, run into a problem when trying to create a prolog list. Iā€™d like to create a list, give it to the engine using the PlQuery object, and get the answer.

All of this has to be made somewhere within the project, in my case itā€™s the main funcion and also all the project (which Iā€™m using to test the environment):

#include "SWI-cpp2.h"
#include <string>
#include <iostream>

using namespace std;

int main(int argc, char** argv)
{
    if (_putenv("SWI_HOME_DIR=C:\\Program Files\\swipl")) return 0;
    if (!PL_initialise(argc, argv))
        PL_halt(1);
    
    
    PlCall("consult('smallProject.pl')",NULL);
    string phrase[5] = { "the","woman","likes","the","man" };
    PlTermv av(2);
    
    PlTerm_tail l(av[1]);
    try {
        for (int i = 0; i < 5; i++) {
            PlTerm t(PlAtom(phrase[i].c_str()));
            if (l.append(t)) cout << "element " << i <<" inserted, being: "<< t.as_string() <<"\n";
        }
        
        if (!l.close()) cerr << "error occurred\n";
        
        PlTerm_var e;
        while (l.next(e))
            cout << "list element: " << e.as_string() << ";\n";

        PlQuery q("smallProject:main", av);
        int res = q.next_solution();
        cout << "out: " << av[0].as_string() << "\n";
        
        switch (res) {
        case PL_S_NOT_INNER:
            cout << "error PL_S_NOT_INNER\n";
            break;
        case PL_S_EXCEPTION:
            cout << "error PL_S_EXCEPTION\n";
            break;
        case PL_S_FALSE:
            cout << "no\n";
            break;
        case PL_S_TRUE:
            cout << "yes\n";
            break;
        case PL_S_LAST:
            cout << "last\n";
            break;
        default:
            cout << "default\n";
            break;

        }
        
        cout << av[0].as_string() << "\n";
    }
    catch (const PlException& e) {
        cerr << e.what() << "\n";
    }
    
}

the purpose of this main is to test the functionalities of the interface, now Iā€™m stuck with PlTerm_tail. Apparently the list is empty although the append calls didnā€™t raise any errors.

I also want to clarify that I canā€™t use the swipl-ld or any kind of compiler option for my project. Hence Iā€™m using the Visual Studio environment with copies of the header files: SWI-cpp2.h, SWI-cpp2-atommap.h, SWI-cpp2-plx.h, SWI-Prolog.h and SWI-Stream.h. And a single resource file: libswipl.dll.a.

The smallProject.pl file has a rule main/2 that wants a list as the second argument and gives, in output, a complex term as the first argument, if i make the query main(T,[ ]). I get ā€œfalseā€. If I make the query main(T,[the,woman,likes,the,man]). I get T = s(np(det(the), n(woman)), vp(v(likes), np(det(the), n(man)))). I donā€™t think providing the full knowledge base is crucial to solving my problem.

When I run the main.cpp file i get this output:

element 0 inserted, being: the
element 1 inserted, being: woman
element 2 inserted, being: likes
element 3 inserted, being: the
element 4 inserted, being: man
out: _
no
_

Maybe in my scenario I shouldnā€™t use PlTerm_tail? Is there another way to create a prolog list object? Thank you for your patience.

PlCall() takes a predicate name and an argument vector in SWI-cpp2.h. So, you get

PlCall("consult", PlTermv(PlTerm_atom("smallProject")));

@peter.ludemann The docs give this example, which seems wrong to me?

load_file(const char *file)
{ return PlCall("compile", PlTermv(file));
}

This is a misconception. l follows the tail of the list. So, Termv av(2) creates a term vector with two variables. PlTerm_tail l(av[1]); creates a copy of the 2nd term handle, i.e., a reference to the same Prolog variable. l.append(X) unifies the variable l points at with [X|NewVar] and updates l to point at NewVar. The l.close() unifies the variable with [] (the empty list). So, after all this work, av[1] references the list and l references the [] at the end of the list.

So, to enumerate the list in C++, we must make a new tail that points at the start. We get this (note the l2):

        PlTerm_var e;
	PlTerm_tail l2(av[1]);
        while (l2.next(e))
            cout << "list element: " << e.as_string() << ";\n";

This will look for a predicate ā€˜smallProject:mainā€™/2 in the user module. If you created smallProject.pl as a module file, you need

 PlQuery("smallProject", "main", av);

PlQuery::next_solution() merely returns TRUE or FALSE. It does not use the extended results code AFAIK. But, maybe @peter.ludemann will correct me here.

With the above fixes and a demo main/2, it works for me.

@peter.ludemann: I wonder whether I missed something. Looking at the code below, I think this will create a new term reference for each iteration, no? The C way to do this would be to create the term_t outside the loop and use one of the PL_put_*() PI functions to destructively update the value pointed at by this term reference. Note that term_t references currently have no way to destroy them except for going out of scope, where the scope is either a foreign predicate or scope created using PlFrame. I think we could implement destruction. If it is the last one created we can make the frame one smaller. Otherwise we could set the reference to some reserved value and make PL_new_term_ref() first scan for references deleted this way. Would that help?

        for (int i = 0; i < 5; i++) {
            PlTerm t(PlAtom(phrase[i].c_str()));
            if (l.append(t)) cout << "element " << i <<" inserted, being: "<< t.as_string() <<"\n";
        }
1 Like

This can be shorter (PlTermv() accepts an atom).

PlCall("consult", PlTermv(PlAtom("smallProject")));

The documentation examples are wrong and Iā€™ll fix them. (The good news: they donā€™t compile.)

If PlQuery was created with PL_Q_EXT_STATUS, the extended results codes are returned (this is documented in the header file; I should update the documentation). Iā€™ve noticed a few other deficiencies in the documentation of PlQuery.

I think youā€™re right. The PL_put_*() functions are available via the API (e.g., PlTerm::put_atom_chars(...)). Iā€™ll add an example to the documentation (and to the tests) and get @jan to review it.

1 Like

But this is probably not good as I think this prevents the atom to be garbage collected, no? At the low level, this should create an atom, bind the term handle to the atom and unregister the atom. After that the atom is protected by the term, but otherwise subject to GC.

This looks rather inconvenient to me :frowning: Fixing this by means of adding PL_free_term_ref() seems more attractive, no?

1 Like

Hmmm ā€¦ the API has a number of ways of creating a term thatā€™s an atom ā€¦ I copied them more-or-less as they were in the old C++ API. In some cases they use PL_put_atom(), in other cases, PL_unify_wchars() or similar.

If I do something like this:

PREDICATE foo(...)
{ static PlAtom small_project("smallProject");
  PlCall("consult", PlTermv(small_project);
  ...
}

then the atom smallProject would never be garbage collected, correct?

So, should I modify Pltermv_atom() to create a garbage-collectable atom, the way @jan suggests?
On the other hand, I do see much problem with any constant in the code not being garbage-collectable; for example, if an atom appears in a Prolog clauses (as opposed to being created by something like atom_chars/2), itā€™d never be garbage collected, correct?

1 Like

PL_new_atom() looks up and possible creates an atom and atomically increments its reference count. An atom is collected if the reference count is zero and it is not accessible from the Prolog stacks. So, to bind a term to an atom created from a string we must do

atom_t a = PL_new_atom(str);
PL_put_atom(term, a);  // or PL_unify_atom()
PL_unregister_atom(a);

This is why we have interfaces to bind terms to an atom created from a string as one operation: it saves typing and it avoids having to go through the API three times. Note that PL_new_atom() must increment the reference count as the atom may otherwise be deleted right after it is created by a concurrently running atom-GC.

Normally, C(++) code does not create atoms explicitly. There are some exceptions, such as creating atoms for constants you want to compare to. In C

static atom_t ATOM_read;
// In install()
ATOM_read = PL_new_atom("read");

After which you can check a term is bound to 'read' using the code below. This avoids getting to the text of an atom as well as string comparison.

if ( PL_get_atom(term, &a) && a == ATOM_read )
2 Likes

This really helped me understand. This is the better way of checking options passed from Prolog to C, and then parsed by PL_scan_options(), right? I was indeed taking the strings and using strcmp() which felt wrong but I didnā€™t know better. Thank you again!

It is faster and more compact. In 99% of the applications the difference is probably not measurable though. It also has the advantage that you define the constants of your code centrally. So yes, I normally define and initialize atoms and functors used by the code using this style.

2 Likes

Apparently this was my mistake, and also the misconception about the tail object. Thank you all for your help and your patience!

The current C++ API is overly complicated for things like creating terms that are atoms (and I got a little confused while working on it, so thereā€™s a good chance there are some errors). Some of the API is the way it is because it tries to provide access to everything in SWI-Prolog.h and that has some historical artefacts (remarkably few, I think, considering how long itā€™s been worked on). The C++ documentation can also be improved.

Iā€™ll try to find some time to review the C++ API and documentation. To start with, Iā€™ll improve the documentation and examples for dealing with lists, also for dealing with PL_scan_options() ā€“ for example, thereā€™s an undocumented PlAtom::unwrap_as_ptr() that can be used.

1 Like

As is, at the C level we have two ways to construct a list of atoms top-down:

int
unify_atom_list(term_t list, char **array, size_t len)
{ term_t tail, head;

  if ( !(tail=PL_copy_term_ref(list)) ||
       !(head=PL_new_term_ref()) )
    return FALSE;

  for(size_t i=0; i<len; i++)
  { if ( !PL_unify_list(tail, head, tail) ||
	 !PL_unify_chars(head, PL_ATOM, (size_t)-1, array[i]) ||
      return FALSE;
  }

  PL_reset_term_refs(tail);
  return TRUE;
}

Or, using local reclaim of term_t (current git version)

int
unify_atom_list(term_t list, char **array, size_t len)
{ term_t tail;

  if ( !(tail=PL_copy_term_ref(list))
    return FALSE;

  for(size_t i=0; i<len; i++)
  { term_t head;

    if ( !(head = PL_new_term_ref()) ||
	 !PL_unify_list(tail, head, tail) ||
	 !PL_unify_chars(head, PL_ATOM, (size_t)-1, array[i]) )
      return FALSE;

    PL_free_term_ref(head);
  }

  PL_free_term_ref(tail);
  return TRUE;
}

If this is immediately in a foreign predicate or created foreign frame, we do not need to verify the PL_copy_term_ref() and PL_new_term_ref() return values as a new environment guarantees that there is room for 10 term handles. We also do not need to free them as the return or destruction of the frame will do that.

I give the second for @peter.ludemann as I think that will work more elegantly with C++. But, it is of course a little slower as we need to (de)allocate the term_t on each iteration.

@peter.ludemann Do you think using the new PL_free_term_ref() is worthwhile? The patch comes at very close to zero cost as long as no term references are freed. Freeing them in ā€œstack orderā€ is realized by shrinking the frame. Otherwise we fill the reference with a reserved value and mark there may be freed references above a given index. PL_new_term_ref() than scans for references from that index and updates the no-free-before index.

If you plan to use that in the C++ interface at some point Iā€™ll backport PL_free_term_ref() to the 9.2 series so we do not have to wait the entire stable cycle to get back in sync.

Donā€™t know. Right now, Iā€™m trying to verify that the constructors for PlTermv, PlTerm, PlAtom do what I think they do (Iā€™m writing a few simplified pieces of code and then tracing with gdb because the C++ rules for invoking base class constructors are a bit complicated) ā€¦ in particular, Iā€™m looking for redundant calls to PL_new_term_ref().

[I wonā€™t be able to do this for a few days]

1 Like

Thanks. I guess that the simple alternative is to define the destructor of PlTerm to call PL_free_term_ref(). That cannot do any harm except for loosing a little performance. The advantage is that all remains nice and simple from the C++ side. You get the most efficient code by minimizing allocated term references and reuse them. That is harder to fit in easy reusable code fragments (I think).

If you think the PL_free_term_ref(). is the way to go we might need something to deal with PL_new_term_refs(). As is, this merely creates N subsequent term references, returning the first. The first one does not know it belongs to an array. We can add a PL_free_term_refs(t0,len) and make PL_new_term_refs() scan for an array of N successive released term references.

Iā€™m reluctant to have C++ destructors do anything ā€“ currently PlTerm is just a wrapper of term_t, whose constructor calls PL_new_term_ref() by default and doesnā€™t call anything if the wrapped value is specified:

PlTerm()         : WrappedC<term_t>(Plx_new_term_ref()) { }
PlTerm(term_t t) : WrappedC<term_t>(t)                  { }

(Plx_new_term_ref() calls PL_new_term_ref() and throws a C++ exception if thereā€™s an error)

Why is that?

Do you have insight when/whether this is used?

I think we are faced with two problems. Well, actually two incarnations of the same issue. One concerns foreign predicates. These create a foreign frame that scope the term references. This is fine, as long as the implementation does not call things in a loop where each iteration creates term references. In that case we should call PL_reset_term_refs() to discard the term references. That is efficient, but rather error prone.

The other is commonly seen if Prolog is embedded. Now, setting up a call to Prolog from C++ needs to reclaim all term references. Wrapping setting up and handling a call in a foreign frame is efficient and good, but often forgotten. This works well for quite a while, but it does build up term references which causes running out of local stack. Also, the term references protect data that is actually garbage.

A destructor for PlTerm would solve both of them (but this should also include PlTermv for the embedded case).

C++ constructors/destructors use the C++ stack; PlTerm uses the Prolog stack (either local or global). Thereā€™s already too much magic happening with the C++ constructors (partly because I factored out the common stuff with WrappedC<...>).

It might make sense to have a different C++ class for terms that follow Prolog alloc/free rules ā€“ similar to classes such as shared_ptr<...> which use garbage collection. But I donā€™t know if thereā€™s enough value in doing this nor what complications might result. (And would we want something similar for atoms, functors, etc.?)

Iā€™ve used it in situations where a function gets a term_t and I want a PlTerm. One example is in PREDICATE:

rc = pl_ ## name ## __ ## arity(PlTermv(arity, PlTerm(t0)));

All the wrapper classes (PlAtom, PlTerm, PlFunctor, etc.) have a constructor that takes the wrapped value (atom_t, term_t, functor_t) - they get this from their base class:

WrappedC(C_t v) : C_(v) { }

Another use is for a ā€œnullā€ value, although that could be handed by a specialized constructor:

PlAtom token(PlAtom::null); // similar to: atom_t token = 0;