Search code examples
prologsignaturepredicatefirst-order-logic

First-order predicate logic in Prolog: Working with Signatures and Arithmetic Terms


I'm working with Terms and Signatures (first-order predicate logic) in Prolog.

I've been given the signature (∅, Z, {add(2), sub(2), mult(2)}). So there's no variables, the constants are integers, and the functions are "add", "sub" and "mult".

The goal is to write the predicate "calc/2", which takes a term (given the signature above) and calculates it according the usual definition of addition, subtraction and multiplication of integers. Example:

?- calc( add(sub(8,2), mult(4,-3)), N).
N = -6

(since ((8 − 2) + (4 ∗ −3)) = −6)

The problem is similar and related to this question, but one big difference is that I need to avoid using the =.. operator to find the name of the functions. Instead, I want to take advantage of the fact that Prolog uses pattern matching on a rule's parameters, which is what I'm completely stuck on. Another thing to note is that "add", "sub", and "mult" are not supposed to be predicates.

My first solution using the =.. operator was (with the help of the answer from the thread linked above):

op(+, add).
op(-, sub).
op(*, mult).

calc(Term, N) :-
    calc2(Term, Expr),
    N is Expr.

calc2(N,N) :- integer(N).
calc2(Term, Expr) :-
    Term =.. [Op_str, Comp1, Comp2],
    op(Op, Op_str),
    calc2(Comp1, Expr1),
    calc2(Comp2, Expr2),
    Expr =.. [Op, Expr1, Expr2].

But since this^ isn't the type of solution I'm looking for it doesn't really help much with my issue.

I can pretty easily perform single calculations (like calc(add(1,2), N)), but I struggle with anything more complicated than that.


Solution

  • To use pattern matching and avoid the univ operator =.., you can rewrite calc2/2 like this:

    calc2(add(Op1, Op2), Expr1 + Expr2) :- 
        calc2(Op1, Expr1),
        calc2(Op2, Expr2).
    calc2(sub(Op1, Op2), Expr1 - Expr2) :-
        calc2(Op1, Expr1),
        calc2(Op2, Expr2).
    calc2(mult(Op1, Op2), Expr1 * Expr2) :-
        calc2(Op1, Expr1),
        calc2(Op2, Expr2).
    

    This makes the code more visual, where additional cases (e.g. power) can be handled by separate clauses. In addition, modern compilers can optimize the generated code.