I need to write a customized function that will be called many times by other fixed functions. In this function, at the first called time, it will return the total number of lin
What you need is some form of caching mechanism. That has to use some global resource somehow. The dynamic database is usually used for this purpose. Here is a very simplistic form of it. More sophisticated techniques in that area are known under the notion of tabling.
:- dynamic(cachedgoal_sol/2).
reset :-
retractall(cachedgoal_sol(_,_)).
eq(A, B) :-
subsumes_term(A, B),
subsumes_term(B, A).
cached_call(Goal) :-
\+ ( cachedgoal_sol(Skel,_), eq(Skel, Goal) ), % No fitting Goal was cached
copy_term(Goal, Skel),
catch(
( Goal,
assertz(cachedgoal_sol(Skel,Goal)),
fail
),
Pat,
(reset, throw(Pat))).
cached_call(Goal) :-
cachedgoal_sol(Skel,XGoal),
eq(Skel, Goal),
XGoal = Goal.
Usage: Start with reset.
Then, wrap Goal
as cached_call(Goal)
. Don't forget to reset when things change!
Here are some explanations:
reset/0
just removes all cached results.
eq/2
is equal up to renaming of variables, provided that the sets of variables of both arguments are disjoint.
cachedgoal_sol/2
is a dynamic predicate. It is used to store solutions (actually: answers) of a particular goal. To this purpose it keeps a copy of the actual goal in the first argument and the actual answer/solution in the second. Note that for one predicate there might be several, different queries. Say: member(X,[a,b,c])
and member(X,[X1,X2,X3])
. These queries will be handled and cached independently of each other.
If a goal has to be cached anew, a copy of the term is created to have a "key" for the cache. Then, the goal is executed and each answer is stored - this is done exhaustively. This is particularly interesting for queries that have more than one answer.
Further the goal is protected with catch/3
catching all errors Pat
. In this manner, all errors that happen to occur while executing the goal will cause a reset
of the cache. This is particularly important for non-terminating queries: cached_call(length(L,N))
would otherwise leave a finite number of solutions in the cache - which would leave the cache in an inconsistent state...
In any case, the first clause always fails. So it is here only for the side effect of updating the cache.
The second clause now uses the cache. Note that it is not possible to XGoal = Goal
"further to the left", because eq/2
has to ensure that we are using only the results of the very same query.
As I said already: This is a very naive way of doing this, but at least it is simple and relatively robust.
As to your original program. You can now write:
..., cached_call(calculateInputFunction(Input, Result)), ...
every time you need that value.