Functional programming 逻辑和函数式编程在gcd实现上的差异

Functional programming 逻辑和函数式编程在gcd实现上的差异,functional-programming,prolog,scheme,logic,clpfd,Functional Programming,Prolog,Scheme,Logic,Clpfd,我目前正在学习编程语言的概念和语用学,因此我觉得我需要帮助区分声明性语言家族的两个分支 考虑以下分别用Scheme和Prolog编写的代码段: ;Scheme (define gcd (lambda (a b) (cond ((= a b) a) ((> a b) (gcd (- a b) b)) (else (gcd (- b a) a))))) 我不明白的是: 这两种不同

我目前正在学习编程语言的概念和语用学,因此我觉得我需要帮助区分声明性语言家族的两个分支

考虑以下分别用Scheme和Prolog编写的代码段:

;Scheme 
(define gcd
    (lambda (a b)
         (cond ((= a b) a)           
               ((> a b) (gcd (- a b) b))
               (else (gcd (- b a) a)))))


我不明白的是:

这两种不同的编程语言是如何工作的 不同


我们在哪里做出区别,以便将它们分类 基于函数或逻辑的编程语言


就我而言,它们做的事情完全一样,调用递归函数直到它终止

从一个例子来看,差别不是很明显。编程语言分为逻辑语言、函数语言、,。。。基于它们所支持的一些特性,因此它们的设计目的是使程序员在每个领域(逻辑、功能…)更容易。例如,命令式编程语言(如c)与面向对象语言(如java、c++)有很大的不同,这里的差异更为明显

更具体地说,在您的问题中,Prolog编程语言采用了逻辑编程的哲学,这对于稍微了解数理逻辑的人来说是显而易见的。Prolog有谓词(而不是基本上几乎相同的函数),它们根据我们定义的“世界”返回true或false,例如我们已经定义了哪些事实和子句,定义了哪些数学事实等等……所有这些都是由数学逻辑继承的(命题和一阶逻辑)。因此,我们可以说,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)。
为真。因此,您可以理解,这都是关于谓词何时成功的问题,因此您可以有多个解决方案,而在函数式编程语言中则不能

  • 函数语言基于函数,因此总是返回相同的值 结果类型。这并不总是存在于Prolog中。您可以有一个谓词
    谓词\u示例(数字,列表)。
    和查询
    谓词\u示例(5,列表)。
    返回列表=…(列表)和查询
    谓词\u示例(数字,[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,尝试了各种可能的方法 因此,在每一步中,它都会尝试你们所有人的三个条款,并将 找到所有可能的解决方案。函数式编程语言 另一方面,like函数应该具有定义良好的唯一步骤 找到解决办法
因此,您可以理解函数式语言和逻辑语言之间的差异可能并不总是显而易见的,但它们基于不同的哲学思维方式


想象一下,在Scheme中解决tic-tac-toe、N-queens问题或人-羊-狼-卷心菜问题是多么困难。

GCD示例只是略微提到了逻辑编程和函数编程之间的区别,因为它们彼此之间的距离远比命令式编程更近。我将集中讨论Prolog和OCaml,但我相信这很有代表性

逻辑变量和统一: Prolog允许表达部分数据结构,例如,在术语
节点(24,左,右)
中,我们不需要指定
代表什么,它们可以是任何术语。函数式语言可以插入一个或一个稍后评估的术语,但在创建术语时,我们需要知道要插入什么

逻辑变量也可以统一(即相等)。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)、约束算法或其他方法)的优点仍有一些讨论
?- 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]
?- 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.
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). ?- gcd(X, Y, 3). X = Y, Y = 3 ; X = 6, Y = 3 ; X = 9, Y = 3 ; X = 12, Y = 3 ; etc. ?- 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 ; etc.