逻辑编程相对于函数式编程的一个重要吸引力在于,您通常可以在多个方向上使用相同的代码。
这意味着您不仅可以在给出输入的情况下询问特定结果,还可以询问解决方案的一般情况。
但是,要使其发挥作用,您必须对表示数据的方式进行一些思考。例如,在您的情况下,表达式中仍然是逻辑变量的任何术语都可能表示给定的数字或原子,其解释方式应不同于普通数字或其他两个术语的加法。这被称为默认表示,因为您必须决定一个变量默认应该表示什么,并且没有办法将其含义仅限于一种可能的情况。
因此,我建议首先更改表示,以便您可以符号区分这两种情况。例如,要在您的情况下表示表达式,让我们采用以下约定:
- 原子由包装器表示
a/1
- 数字由 wrapper 表示
n/1
。
- 和已经存在的情况一样,
(+)/2
应表示添加两个表达式。
所以,一个像这样的默认术语b+10
现在应该写成:a(b)+n(10)
。请注意包装器的使用,a/1
并n/1
明确我们正在处理哪种情况。这种表示称为 clean。包装器是任意选择的(尽管是助记符),我们可以使用完全不同的包装器,例如atom/1
and number/1
, or atm/1
and nmb/1
。关键属性只是我们现在可以通过它们最外层的函子和数量来象征性地区分不同的情况。
现在的关键优势:使用这样的约定,我们可以编写例如:a(X)+n(Y)
. 这是对早期术语的概括。但是,它携带的信息远不止X+Y
,因为在后一种情况下,我们已经忘记了这些变量代表什么,而在前一种情况下,这种区别仍然存在。
现在,假设在表达式中使用了这个约定,那么描述不同的情况就变得直截了当:
表达式_结果(n(N),_,_,n(N))。
表达式_结果(a(A),A,N,n(N))。
表达式_结果(a(A),Var,_,a(A)):-
差异(A,Var)。
expression_result(X+Y, Var, Val, R) :-
expression_result(X, Var, Val, RX),
expression_result(Y, Var, Val, RY),
加法(RX,RY,R)。
加法(n(X),n(Y),n(Z)):- Z #= X + Y。
加法(a(X),Y,a(X)+Y)。
加法(X,a(Y),X+a(Y))。
请注意,我们现在可以使用模式匹配来区分情况。不再需要 if-then-elses,也不再需要atom/1
ornumber/1
测试。
您的测试用例按预期工作:
?- expression_result(a(a)+n(10), a, 1, 结果)。
结果 = n(11) ;
错误的。
?- expression_result(a(a)+n(10), b, 1, 结果)。
结果 = a(a)+n(10) ;
错误的。
现在的关键优势是:有了这样一个纯程序(有关更多信息,请参阅logical-purity),我们还可以问“一般结果是什么样的?”
?- expression_result(Expr, Var, N, R)。
表达式 = R, R = n(_1174) ;
Expr = a(Var),
R = n(N) ;
表达式 = R, R = a(_1698),
差异(_1698,变量);
表达式 = n(_1852)+n(_1856),
R = n(_1896),
_1852+_1856#=_1896 ;
Expr = n(_2090)+a(Var),
R = n(_2134),
_2090+N#=_2134。
在这里,我对所有参数都使用了逻辑变量,并且我从这个程序中得到了相当普遍的答案。这就是为什么我将clpfd约束用于声明性整数算术。
因此,您可以通过使用干净的表示并使用上面的代码轻松解决您的直接问题。
只剩下一个很小的挑战:也许您实际上想要使用默认表示,例如c+10
( 而不是a(c)+n(10)
)。然后您面临的任务是将默认表示转换为干净的表示,例如通过 predicate defaulty_clean/2
。我把它作为一个简单的练习。一旦你有了一个干净的表示,你就可以使用上面的代码而不用做任何更改。