Note that acyclic_term/1 is ISO, but cyclic_term/1 is not.
Consider the evolution of a term from "most uninstantiated" to "ground":
uninstantiated
|
V
+--------------+-------------+
| |
V |
nonground noncyclic |
| |
+----------------------------+
| |
| V
| nonground cyclic
| |
V V
ground acylic ground cyclic
Try
examine(X) :-
(ground(X) -> format("ground,",[]) ; format("non-ground,",[])),
(cyclic_term(X) -> format("cyclic,",[]) ; format("not cyclic,",[])),
(acyclic_term(X) -> format("acyclic~n",[]) ; format("not acyclic~n",[])).
X=_ , examine(X). % uninstantiated
X=s(_) , examine(X). % nonground, no cycles
X=s(a) , examine(X). % ground, no cycles
X=s(X) , examine(X). % ground, cycles
X=s(X,_) , examine(X). % nonground, cylces
To see what the predicates say at every point in the evolution of instantiation
X=_
not "cyclic", "acyclic"
|
V
+--------------+-------------+
| |
V |
X=s(_) |
not "cyclic", "acyclic" |
| |
+----------------------------+
| |
| V
| X=s(X,_)
| "cyclic", not "acyclic"
| |
V V
X=s(a) X=s(X)
not "cyclic", "acyclic" "cyclic", not "acyclic"
cyclic_term/1 is a bit optimistic as it says "not cyclic" even though the same term can become cyclic by instantiations later.
The predicates cyclic_term/1 and acyclic_term/1 are in fact "second order": They say something about the current state of computation, not about the term they are examining.
One would like to see these predicates:
cyclic | cyclic_term/1 | acyclic_term/1 | acyclic_forever | |
| uninstantiated | throw | false (could change) | true (could change) | false |
| nonground acyclic | throw | false (could change) | true (could change) | false |
| ground acylic | false (for sure | false (for sure) | true (for sure) | true |
| nonground cyclic | true (for sure) | true (for sure) | false (for sure) | false |
| ground cyclic | true (for sure) | true (for sure) | false (for sure) | false |
The above have been implemented in checks.pl