逻辑编程和函数编程之间的区别

Hes*_*era 68 haskell functional-programming prolog

我一直在阅读许多文章试图理解功能和逻辑编程之间的区别,但到目前为止我能够做出的唯一推论是逻辑编程通过数学表达式定义程序.但是这样的事情与逻辑编程无关.

我真的很感激功能和逻辑编程之间的差异.

Tha*_*dis 60

我不会说逻辑编程通过数学表达式定义程序; 这听起来更像是函数式编程.逻辑编程使用逻辑表达式(好吧,最终逻辑是数学).

在我看来,功能和逻辑编程之间的主要区别在于"构建块":函数编程使用函数,而逻辑编程使用谓词.谓词不是函数; 它没有返回值.根据它的参数值,它可能是真的,也可能是假的; 如果某些值未定义,它将尝试查找使谓词成立的值.

Prolog特别使用一种特殊形式的逻辑子句,名为Horn子句,属于一阶逻辑; Hilog使用更高阶逻辑的子句.

当你编写prolog谓词时,你正在定义一个horn子句: foo :- bar1, bar2, bar3.意味着如果bar1,bar2和bar3为真,则foo为真.请注意,我没有说是否,仅当; 你可以为一个谓词有多个子句:

foo:-
   bar1.
foo:-
  bar2.
Run Code Online (Sandbox Code Playgroud)

表示如果bar1为true或bar2为true,则foo为true

有人说逻辑编程是函数式编程的超集,因为每个函数都可以表示为谓词:

foo(x,y) -> x+y.
Run Code Online (Sandbox Code Playgroud)

可写成

foo(X, Y, ReturnValue):-
   ReturnValue is X+Y.
Run Code Online (Sandbox Code Playgroud)

但我认为这些陈述有点误导

逻辑和功能之间的另一个区别是回溯.在函数编程中,一旦输入函数体,就不会失败并转到下一个定义.例如,你可以写

abs(x) -> 
   if x>0 x else -x
Run Code Online (Sandbox Code Playgroud)

甚至使用警卫:

abs(x) x>0 -> x;
abs(x) x=<0 -> -x.
Run Code Online (Sandbox Code Playgroud)

但你不能写

abs(x) ->
   x>0,
   x;
abs(x) ->
   -x.
Run Code Online (Sandbox Code Playgroud)

另一方面,在Prolog你可以写

abs(X, R):-
   X>0,
   R is X.
abs(X, R):-
   R is -X.
Run Code Online (Sandbox Code Playgroud)

如果那时你打电话abs(-3, R),Prolog会尝试第一个子句,并且当执行到达该-3 > 0点时失败但你不会得到错误; Prolog将尝试第二个条款并返回R = 3.

我不认为函数式语言不可能实现类似的东西(但我没有使用过这样的语言).

总而言之,虽然两种范式都被认为是陈述性的,但它们却截然不同; 如此不同,比较他们感觉就像比较功能和命令式的风格.我建议尝试一些逻辑编程; 这应该是一个令人难以置信的经历.但是,你应该尝试理解哲学而不是简单地编写程序; Prolog允许您以功能性甚至命令式的方式编写(具有可怕的结果).

  • "我不认为函数式语言不可能实现类似的东西":使用Lisp编写的Prolog解释器或用Prolog编写的Lisp解释器很容易找到.两者都是图灵完整的语言.还有功能逻辑编程语言Curry结合了两种范例. (9认同)
  • Mercury还具有返回值的真实函数(包括高阶函数),并支持回溯.从这个意义上讲,您可以将其视为实现回溯的函数式语言.然而,它也有谓词并使用类似prolog的语法,逻辑编程是其中的常规编程模式.我觉得这个组合非常有用.http://www.mercury.csse.unimelb.edu.au/ (5认同)
  • S(X).关于高阶能力:很长一段时间,Prolog对[tag:meta-predicate]的支持与功能语言相比还有很多不足之处,包括功能,性能和可移植性.最近(2012年,确切地说)ISO-Prolog在完成TC2("技术勘误2")后朝这个方向发展.今天,这些功能很普遍,拿起Prolog手册并查看`call/2..8`.而且......用它! (2认同)

soc*_*a23 28

简而言之:

在函数式编程中,您的程序是一组函数定义.每个函数的返回值都被计算为一个数学表达式,可能使用传递的参数和其他定义的函数.例如,您可以定义一个factorial函数,该函数返回给定数字的阶乘:

factorial 0 = 1                       // a factorial of 0 is 1
factorial n = n * factorial (n - 1)   // a factorial of n is n times factorial of n - 1 
Run Code Online (Sandbox Code Playgroud)

在逻辑编程中,您的程序是一组谓词.谓词通常被定义为子句集,其中每个子句可以使用数学表达式,其他定义的谓词和命题演算来定义.例如,您可以定义一个"阶乘"谓词,只要第二个参数是第一个阶乘,它就会成立:

factorial(0, 1).               // it is true that a factorial of 0 is 1
factorial(X, Y) :-             // it is true that a factorial of X is Y, when all following are true:
    X1 is X - 1,                   // there is a X1, equal to X - 1,
    factorial(X1, Z),              // and it is true that factorial of X1 is Z, 
    Y is Z * X.                    // and Y is Z * X
Run Code Online (Sandbox Code Playgroud)

两种样式都允许在程序中使用数学表达式.


fal*_*lse 22

首先,功能和逻辑编程之间存在许多共性.也就是说,在一个社区中开发的许多概念也可以在另一个社区中使用.这两种范式都是从粗略的实施开始,并努力实现纯洁.

但是你想知道这些差异.

所以我将一方面采用Haskell而另一方面采用Prolog.实际上所有当前的Prolog系统都提供某种约束,如B,Ciao,ECLiPSe,GNU,IF,SICStus,SWI,YAP,XSB.为了论证,我将使用一个非常简单的约束,dif/2意味着不平等,即使在第一个Prolog实现中也存在 - 所以我不会使用比它更高级的东西.

缺少什么功能编程

最根本的区别在于变量的概念.在函数式编程中,变量表示具体值.此值不能完全定义,但只有那些定义的部分才能用于计算.在Haskell中考虑一下:

> let v = iterate (tail) [1..3] 
> v
[[1,2,3],[2,3],[3],[],*** Exception: Prelude.tail: empty list
Run Code Online (Sandbox Code Playgroud)

在第4个元素之后,该值未定义.不过,您可以安全地使用前4个元素:

> take 4 v
[[1,2,3],[2,3],[3],[]]
Run Code Online (Sandbox Code Playgroud)

请注意,功能程序中的语法被巧妙地限制,以避免变量未定义.

在逻辑编程中,变量不需要引用具体值.所以,如果我们想要一个包含3个元素的列表,我们可能会说:

?- length(Xs,3).
Xs = [_G323, _G326, _G329].
Run Code Online (Sandbox Code Playgroud)

在这个答案中,列表的元素是变量.这些变量的所有可能实例都是有效的解决方案 喜欢Xs = [1,2,3].现在,让我们说第一个元素应该与其余元素不同:

?- length(Xs,3), Xs = [X|Ys], maplist(dif(X), Ys).
Xs = [X, _G639, _G642],
Ys = [_G639, _G642],
dif(X, _G642),
dif(X, _G639).
Run Code Online (Sandbox Code Playgroud)

稍后,我们可能会要求Xs所有元素都相等.在我写出来之前,我会单独尝试:

?- maplist(=(_),Xs).
Xs = [] ;
Xs = [_G231] ;
Xs = [_G231, _G231] ;
Xs = [_G231, _G231, _G231]  ;
Xs = [_G231, _G231, _G231, _G231] .
Run Code Online (Sandbox Code Playgroud)

看到答案总是包含相同的变量?现在,我可以结合两个查询:

?- length(Xs,3), Xs = [X|Ys], maplist(dif(X), Ys), maplist(=(_),Xs).
false.
Run Code Online (Sandbox Code Playgroud)

所以我们在这里展示的是没有3个元素列表,其中第一个元素与其他元素不同,所有元素都相等.

这种普遍性允许开发几种约束语言,这些语言作为库提供给Prolog系统,最突出的是CLPFDCHR.

在函数式编程中没有直接的方法来获得类似的功能.你可以模仿事物,但仿真并不完全相同.

缺少什么逻辑编程

但是逻辑编程中缺少很多东西使函数式编程变得如此有趣.特别是:

高阶编程:函数式编程在这里有着悠久的传统,并且已经开发出了丰富的习语.对于Prolog,第一个提案可以追溯到20世纪80年代初,但它仍然不常见.至少ISO Prolog现在已经申请了同名申请call/2, call/3 ....

Lambdas:同样,可以在这个方向上扩展逻辑编程,最突出的系统是Lambda Prolog.最近,lambdas也为ISO Prolog开发.

类型系统:曾经有过像Mercury这样的尝试,但它没有那么多.并且没有具有与类型类相比的功能的系统.

纯度:Haskell完全是纯粹的,函数Integer - > Integer是一个函数.周围没有精美的印刷品.你仍然可以执行副作用.可比方法的发展非常缓慢.

功能和逻辑编程或多或少重叠的领域很多.例如回溯和lazyness和list解析,懒惰评估和freeze/2,when/2,block.DCG和monads.我将把这些问题留给别人讨论......

  • 这是对比 Haskell 和 Prolog 的一个非常好的答案。我不太确定它是否真的触及了逻辑和函数范式的核心,而是陷入了特定的语言细节中。例如,还有其他不懒惰的函数语言,并且有些逻辑编程语言没有未绑定的变量别名,因此无法执行您的示例。水星是我所知道的;它几乎具有您所说的逻辑编程作为一个整体所缺乏的一切,但您的长度/映射列表示例在其中不起作用。仍然绝对是逻辑编程。 (2认同)

Ben*_*Ben 17

逻辑编程和函数编程使用不同的"隐喻"进行计算.这通常会影响您对生成解决方案的看法,有时意味着不同的算法自然而然地成为功能程序员而不是逻辑程序员.

两者都基于数学基础,为"纯"代码提供更多好处; 不具有副作用的代码.两种范式都有语言可以强制实现纯度,以及允许不受约束的副作用的语言,但在文化上,这些语言的程序员往往仍然重视纯度.

我将考虑append在逻辑和函数编程中进行相当基本的操作,将列表附加到另一个列表的末尾.

在函数式编程中,我们可能会考虑append这样的事情:

append [] ys = ys
append (x:xs) ys = x : append xs ys
Run Code Online (Sandbox Code Playgroud)

在逻辑编程中,我们可能会考虑append这样的事情:

append([], Ys, Ys).
append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).
Run Code Online (Sandbox Code Playgroud)

这些实现了相同的算法,甚至基本上以相同的方式工作,但它们"意味着"非常不同的东西.

函数append定义了追加ys到末尾的结果列表xs.我们将其append视为从两个列表到另一个列表的函数,运行时系统用于在我们在两个列表上调用函数时计算函数的结果.

逻辑append定义了三个列表之间的关系,如果第三个列表是第一个列表的元素,后跟第二个列表的元素,则为true.我们认为任何3个给定列表append谓词都是真或假,并且运行时系统旨在找到这样的值,当我们使用绑定到特定列表的某些参数调用它时,这些谓词将成为真,而某些参数则保持未绑定状态.

使逻辑append不同的是,您可以使用它来计算将一个列表附加到另一个列表而产生的列表,但您也可以使用它来计算您需要附加到另一个列表的列表以获取第三个列表(或者是否不存在这样的列表),或者计算您需要附加另一个列表以获得第三个列表的列表,或者为您提供两个可以附加在一起以获得给定三分之一的列表(以及探索所有可能的列表)这样做的方法).

虽然相当于你可以做任何事情,你可以在另一个中做,但它们会导致不同的思考你的编程任务的方式.要在函数式编程中实现某些功能,您需要考虑如何从其他函数调用的结果(您可能还必须实现)中生成结果.要在逻辑编程中实现某些东西,你要考虑你的参数之间的关系(其中一些是输入,一些是输出,而不一定是从调用到调用的相同)将意味着所需的关系.