3
votes

I wrote a test program with bindings (facts) between atoms and numbers.

bind(a, 3).
bind(b, 4).
bind(c, 5).

As part of a toy interpreter, I want to be able to perform additions on these atoms using Prolog's native arithmetic operators. For instance, I want to be able to run this query:

% val(X) is the value bound to X
?- X is val(a) + val(b).
X = 7.

However, I'm struggling to find a way to allow this addition. My first approach would have been this one:

% val(X, Y): Y is the value bound to X
val(X, Y) :- bind(X, Y).

% Make val an arithmetic function
:- arithmetic_function(val/1).

However, arithmetic_function/1 is no longer part of Prolog (or at least SWI-Prolog says it's deprecated), so I can't use it. Then I believed the best solution would be to overload the + operator to take this into account:

% val(X, Y): Y is the value bound to X
val(val(X), Y) :- bind(X, Y).

% Overload the + operator
+(val(_X, XVal), val(_Y, YVal)) :- XVal + YVal.

But here I've got my syntax all messed up because I don't really know how to overload a native arithmetic operation. When I type in the sample query from before, SWI-Prolog says ERROR: Arithmetic: ``val(a)' is not a function.

Would you have hints about a possible solution or a better approach or something I missed?

3

3 Answers

4
votes

From the docs, I tought you should use function_expansion/3. But I'm unable to get it to work, instead, goal_expansion could do, but isn't very attractive... for instance, if you save the following definitions in a file bind.pl (just to say)

:- module(bind, [test/0]).

:- dynamic bind/2.

bind(a, 3).
bind(b, 4).
bind(c, 5).

% :- multifile user:goal_expansion/2.
user:goal_expansion(val(X), Y) :- bind(X, Y).
user:goal_expansion(X is Y, X is Z) :- expand_goal(Y, Z).
user:goal_expansion(X + Y, U + V) :- expand_goal(X, U), expand_goal(Y, V).

test :-
     X is val(a) + val(b), writeln(X).

and consult it, you can run your test:

?- test.
7

edit

after Paulo suggestion, here is an enhanced solution, that should work for every binary expression.

user:goal_expansion(X is Y, X is Z) :- expr_bind(Y, Z).

expr_bind(val(A), V) :- !, bind(A, V).
expr_bind(X, Y) :-
     X =.. [F, L, R],  % get operator F and Left,Right expressions
     expr_bind(L, S),  % bind Left expression
     expr_bind(R, T),  % bind Right expression
     Y =.. [F, S, T].  % pack bound expressions back with same operator
expr_bind(X, X).       % oops, I forgot... this clause allows numbers and variables

having defined user as target module for goal_expansion, it works on the CLI:

?- R is val(a)*val(b)-val(c).
R = 7.

edit

now, let's generalize to some other arithmetic operators, using the same skeleton expr_bind uses for binary expressions:

user:goal_expansion(X, Y) :-
     X =.. [F,L,R], memberchk(F, [is, =<, <, =:=, >, >=]),
     expr_bind(L, S),
     expr_bind(R, T),
     Y =.. [F, S, T].

and unary operators (I cannot recall no one apart minus, so I show a simpler way than (=..)/2):

...
expr_bind(-X, -Y) :- expr_bind(X, Y).
expr_bind(X, X).

Now we get

?- -val(a)*2 < val(b)-val(c).
true.
3
votes

One way to do it is using Logtalk parametric objects (Logtalk runs on SWI-Prolog and 11 other Prolog systems; this makes this solution highly portable). The idea is to define each arithmetic operation as a parametric object that understands an eval/1 message. First we define a protocol that will be implemented by the objects representing the arithmetic operations:

:- protocol(eval).

    :- public(eval/1).

:- end_protocol.

The basic parametric object understands val/1 and contains the bind/2 table:

:- object(val(_X_), implements(eval)).

    eval(X) :-
         bind(_X_, X).

    bind(a, 3).
    bind(b, 4).
    bind(c, 5).

:- end_object.

I exemplify here only the implementation for arithmetic addition:

:- object(_X_ + _Y_, implements(eval)).

    eval(Result) :-
        _X_::eval(X), _Y_::eval(Y),
        Result is X + Y.

:- end_object.

Sample call (assuming the entities above are saved in an eval.lgt file):

% swilgt
...
?- {eval}.
% [ /Users/pmoura/Desktop/eval.lgt loaded ]
% (0 warnings)
true.

?- (val(a) + val(b))::eval(R).
R = 7.

This can be an interesting solution if you plan to implement more functionality other than expression evaluation. E.g. a similar solution but for symbolic differentiation of arithmetic expressions can be found at:

https://github.com/LogtalkDotOrg/logtalk3/tree/master/examples/symdiff

This solution will also work in the case of runtime generated expressions (term-expansion based solutions usually only work at source file compile time and at the top-level).

If you're only interested in expression evaluation, Capelli's solution is more compact and retains is/2 for evaluation. It can also be made more portable if necessary using Logtalk's portable term-expansion mechanism (but note the caveat in the previous paragraph).

1
votes

This is perhaps not exactly what I was looking for, but I had an idea:

compute(val(X) + val(Y), Out) :-
  bind(X, XVal),
  bind(Y, YVal),
  Out is XVal + YVal.

Now I can run the following query:

?- compute(val(a) + val(c), Out).
Out = 8.

Now I need to define compute for every arithmetic operation I'm interested in, then get my interpreter to run expressions through it.