逻辑和函数式编程在gcd实现上的区别
Posted
技术标签:
【中文标题】逻辑和函数式编程在gcd实现上的区别【英文标题】:Difference in implementation of gcd between logic and functional programming 【发布时间】:2017-02-18 03:07:08 【问题描述】:我目前正在学习编程语言的概念和语用学,因此我觉得我需要帮助来区分声明性语言家族的两个子分支。
考虑以下分别用 Scheme 和 Prolog 编写的代码 sn-ps:
;Scheme
(define gcd
(lambda (a b)
(cond ((= a b) a)
((> a b) (gcd (- a b) b))
(else (gcd (- b a) a)))))
%Prolog
gcd(A, B, G) :- A = B, G = A.
gcd(A, B, G) :- A > B, C is A-B, gcd(C, B, G).
gcd(A, B, G) :- B > A, C is B-A, gcd(C, A, G).
我不明白的是:
这两种不同的编程语言如何表现 不一样?
我们在哪里有所作为,以便它们被分类 函数式还是基于逻辑的编程语言?
就我而言,它们做的事情完全相同,调用递归函数直到它终止。
【问题讨论】:
【参考方案1】:由于您在逻辑编程版本中使用了非常低级的谓词,因此您无法轻易看到逻辑编程比函数式编程提供的更多通用性。
考虑一下这个略微编辑的代码版本,它使用CLP(FD)约束进行声明性整数算术而不是低级您当前使用的算术:
gcd(A,A,A)。 gcd(A, B, G) :- A #> B, C #= A - B, gcd(C, B, G)。 gcd(A, B, G) :- B #> A, C #= B - A, gcd(C, A, G)。重要的是,我们可以将其用作真正的关系,这在所有方向都是有意义的。
例如,我们可以问:
是否存在两个整数
X
和Y
使得它们的 GCD 为 3?
也就是说,我们可以在other方向too使用这个关系!给定两个整数,我们不仅可以计算它们的 GCD。不!我们也可以问,使用相同的程序:
?- gcd(X, Y, 3)。 X = Y,Y = 3; X = 6, Y = 3; X = 9, Y = 3; X = 12, Y = 3; 等等。我们还可以发布更一般的查询并仍然获得答案:
?- gcd(X, Y, Z)。 X = Y,Y = Z; Y = Z, Z#=>X+ -1, 2*Z#=X ; Y = Z, _1712+Z#=X, Z#=>X+ -1, Z#=>_1712+ -1, 2*Z#=_1712 ; 等等。这是一个真正的关系,它比两个参数的函数更一般!
更多信息请参见clpfd。
【讨论】:
【参考方案2】: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 总是唯一的事实,这就是为什么您无法在函数式编程和逻辑编程之间找到太多差异的原因。
总而言之:逻辑编程具有允许更轻松的模式匹配、可逆性和探索搜索树的多种解决方案的变量。这是以复杂的流量控制为代价的。根据问题,执行有时会受到限制的回溯执行或向函数式语言添加回溯会更容易。
【讨论】:
【参考方案3】:从一个例子来看,区别不是很明显。编程语言根据它们支持的一些特性被分类为逻辑、功能、...,因此它们的设计是为了让每个领域的程序员更容易(逻辑、功能...)。例如,命令式编程语言(如 c)与面向对象(如 java、C++)非常不同,这里的差异更加明显。
更具体地说,在您的问题中,Prolog 编程语言采用了他的逻辑编程哲学,这对于稍微了解数学逻辑的人来说是显而易见的。 Prolog 有谓词(而不是函数——基本上几乎相同)根据我们定义的“世界”返回真或假,例如我们已经定义了什么事实和子句,什么数学事实被定义等等......所有这些东西都是由数理逻辑(命题和一阶逻辑)继承的。所以我们可以说 Prolog 被用作逻辑模型,它使逻辑问题(如游戏、谜题......)更容易解决。此外,Prolog 还具有通用语言所具有的一些特性。例如,您可以在示例中编写一个程序来计算 gcd:
gcd(A, B, G) :- A = B, G = A.
gcd(A, B, G) :- A > B, C is A-B, gcd(C, B, G).
gcd(A, B, G) :- B > A, C is B-A, gcd(C, A, G).
在您的程序中,如果 G 与 A、B 的 GCD 合一并且您使用多个子句来匹配所有情况,则在您的程序中使用谓词 gcd 返回 TRUE。当您查询 gcd(2,5,1).
时将返回 True(注意,在 shceme 等其他语言中,您不能将结果作为参数给出),而如果您查询 gcd(2,5,G).
它将 G 与 A、B 的 gcd 统一并返回 1,它是就像问 Prolog 什么应该是 G 才能使 gcd(2,5,G).
为真。因此,您可以理解,这完全取决于谓词何时成功,因此您可以有多个解决方案,而在函数式编程语言中则不能。
predicate_example(Number,List).
并查询 predicate_example(5,List).
它返回 List=... (一个列表)并查询
predicate_example(Number,[1,2,3]).
并返回 N=...(一个数字)。
结果应该是唯一的,在数学中,函数就是关系
在一组输入和一组允许的输出之间,每个输入都与恰好一个输出相关
应该明确什么参数是要返回的变量
例如 gcd 函数的类型为:N * N -> R
,因此获取属于 N(自然数)的 A、B 参数并返回 gcd。但是 prolog(在您的程序中进行了一些更改)可能会返回参数 A,因此查询 gcd(A,5,1).
将给出所有可能的 A 使得谓词 gcd 成功,A=1,2,3,4,5。
Prolog 为了找到 gcd 尝试了所有可能的选择
点所以在每一步它都会尝试你所有的三个子句,并且会
找到所有可能的解决方案。函数式编程语言
另一方面,类似的功能应该有很好的独特定义步骤
找到解决方案。
因此您可以理解,函数式语言和逻辑语言之间的差异可能并不总是可见的,但它们基于不同的哲学思维方式。
想象一下在 Scheme 中解决井字游戏或 N 皇后问题或人山羊狼卷心菜问题会有多难。
【讨论】:
以上是关于逻辑和函数式编程在gcd实现上的区别的主要内容,如果未能解决你的问题,请参考以下文章