GCD 示例只是简单地触及了逻辑编程和函数式编程之间的区别,因为它们之间的关系比命令式编程更接近。我会专注于Prolog和OCaml,但我相信它很有代表性。
逻辑变量和统一:
Prolog 允许表达部分数据结构,例如在术语node(24,Left,Right) 中,我们不需要指定Left 和Right 代表什么,它们可能是任何术语。函数式语言可能会插入 lazy function 或 thunk,稍后会对其进行评估,但在创建术语时,我们需要知道要插入什么。
逻辑变量也可以统一(即相等)。 OCaml 中的搜索功能可能如下所示:
let rec find v = function
| [] -> false
| x::_ when v = x -> true
| _::xs (* otherwise *) -> find v xs
虽然 Prolog 实现可以使用统一而不是 v=x:
member_of(X,[X|_]).
member_of(X,[_|Xs]) :-
member_of(X,Xs).
为了简单起见,Prolog 版本有一些缺点(见下文回溯)。
回溯:
Prolog 的优势在于连续实例化可以轻松撤消的变量。如果您使用变量尝试上述程序,Prolog 将为您返回所有可能的值:
?- member_of(X,[1,2,3,1]).
X = 1 ;
X = 2 ;
X = 3 ;
X = 1 ;
false.
当您需要探索搜索树时,这特别方便,但它是有代价的。如果我们没有指定列表的大小,我们将连续创建所有满足我们属性的列表 - 在这种情况下是无限多的:
?- member_of(X,Xs).
Xs = [X|_3836] ;
Xs = [_3834, X|_3842] ;
Xs = [_3834, _3840, X|_3848] ;
Xs = [_3834, _3840, _3846, X|_3854] ;
Xs = [_3834, _3840, _3846, _3852, X|_3860] ;
Xs = [_3834, _3840, _3846, _3852, _3858, X|_3866] ;
Xs = [_3834, _3840, _3846, _3852, _3858, _3864, X|_3872]
[etc etc etc]
这意味着您需要更加小心地使用 Prolog,因为终止更难控制。特别是,旧式方法(cut 运算符!)很难正确使用,并且仍然有一些关于最近方法的优点的讨论(延迟目标(例如,dif)、约束算法或具体化 if) .在函数式编程语言中,回溯通常使用堆栈或回溯状态单子来实现。
可逆程序:
也许使用 Prolog 的另一个开胃菜:函数式编程有一个评估方向。我们可以只使用find 函数来检查某个v 是否是列表的成员,但我们不能询问哪些列表满足了这一点。在 Prolog 中,这是可能的:
?- Xs = [A,B,C], member_of(1,Xs).
Xs = [1, B, C],
A = 1 ;
Xs = [A, 1, C],
B = 1 ;
Xs = [A, B, 1],
C = 1 ;
false.
这些正是具有三个元素的列表,其中包含(至少)一个元素1。不幸的是,标准算术谓词是不可逆的,再加上两个数字的 GCD 总是唯一的事实,这就是为什么您无法在函数式编程和逻辑编程之间找到太多差异的原因。
总而言之:逻辑编程具有允许更轻松的模式匹配、可逆性和探索搜索树的多种解决方案的变量。这是以复杂的流量控制为代价的。根据问题,执行有时会受到限制的回溯执行或向函数式语言添加回溯会更容易。