逻辑编程与功能编程之间的区别


71

我读过许多文章试图理解功能和逻辑编程之间的区别,但是到目前为止,我唯一能得出的结论就是逻辑编程通过数学表达式定义程序。但是这种事情与逻辑编程无关。

我真的很希望能对函数式编程与逻辑编程之间的区别有所了解。


22
您是否尝试过使用两种语言进行编程?差异将变得明显。
Marcin

3
可以说逻辑PL(即Prolog)是一种特定于域的功能语言,适用于某些搜索和统一问题。
Ingo

16
可以说功能语言(例如Haskell)是一种领域特定的逻辑编程语言,适用于不需要大量搜索或声明性一般性的某些问题(即,功能只能在一个方向上使用,而不能作为一般关系使用)可以在所有方向使用)。

2
@AndersonGreen可以用任何其他图灵完整的编程语言实现任何风格的编程。那不是特别有趣。
Marcin

2
幸运的是,@ Marcin是几种功能逻辑编程语言,它们以更方便的方式组合了这些范例。
安德森·格林

Answers:


63

我不会说逻辑编程是通过数学表达式定义程序的。听起来更像是函数式编程。逻辑编程使用逻辑表达式(当然,逻辑最终是数学)。

在我看来,功能和逻辑编程之间的主要区别是“构建块”:功能编程使用函数,而逻辑编程使用谓词。谓词不是函数;它没有返回值。取决于参数的值,它可以是true或false;如果某些值未定义,它将尝试查找使谓词为真的值。

Prolog特别使用一种特殊形式的逻辑子句,称为Horn子句,它们属于一阶逻辑。Hilog使用更高阶逻辑的子句。

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

foo:-
   bar1.
foo:-
  bar2.

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

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

foo(x,y) -> x+y.

可以写成

foo(X, Y, ReturnValue):-
   ReturnValue is X+Y.

但我认为这样的说法有点误导

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

abs(x) -> 
   if x>0 x else -x

甚至使用警卫:

abs(x) x>0 -> x;
abs(x) x=<0 -> -x.

但是你不能写

abs(x) ->
   x>0,
   x;
abs(x) ->
   -x.

另一方面,在Prolog中,您可以编写

abs(X, R):-
   X>0,
   R is X.
abs(X, R):-
   R is -X.

如果再调用abs(-3, R),Prolog将尝试第一个子句,并在执行到该-3 > 0点时失败,但不会收到错误;Prolog将尝试第二个子句并返回R = 3

我认为功能语言不可能实现类似的功能(但我没有使用过这种语言)。

总而言之,尽管这两种范例都被视为声明式的,但它们却大不相同。差异如此之大,以至于比较起来就像是比较功能性和命令式风格。我建议尝试一些逻辑编程。这应该是令人难以置信的经历。但是,您应该尝试理解其原理,而不仅仅是编写程序。Prolog允许您以实用甚至命令式的风格编写(结果令人吃惊)。


10
“我不认为功能语言不可能实现类似的东西”:用Lisp编写的Prolog解释器或用Prolog编写的Lisp解释器很容易找到。两者都是图灵完备的语言。还有一种功能逻辑编程语言Curry结合了两种范例。
twinterer

@twinterer我的意思是可能存在一种带有回溯功能的语言(您的第二种解释)。咖喱看起来很有趣!
Thanos Tintinidis

5
Mercury还具有返回值的真函数(包括高阶函数),并支持回溯。从这个意义上讲,您可以将其视为已实现回溯的功能语言。但是,它也具有谓词并且使用类似于prolog的语法,并且逻辑编程是其中的常规编程模式。我发现这种组合非常有用。mercury.csse.unimelb.edu.au

3
s(X)。关于高阶功能:长期以来,与功能语言相比,Prolog对元谓词的支持在功能,性能和可移植性方面都远远不够。最近(准确地说,是在2012年),随着TC2(“技术勘误2”)的完成,ISO-Prolog朝着这个方向发展了。如今,这些功能已广泛使用,请查阅Prolog手册并进行查找call/2..8。而且...使用它!
重复

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 

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

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

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


我想看看之间的区别的解释=,并:-在这里,我想这是理解的区别的关键。无论如何,+ 1都是一个好的答案。
luqui 2011年

@ socha23:此示例并未说明这两种范例之间的大部分差异,因为factorial(X,3)会产生实例化错误。因此,与功能性程序的唯一区别是,在Prolog中,您会动态地得到该错误。但理想情况下,您也可以证明上述查询没有解决方案。实际上,像library(clpfd)这样的约束条件都能为您提供正确的答案。请参阅n_factorial/2。有两种实现:天真的和更有效的实现
错误的

哇,今天我一直在浏览基本介绍,以序言的方式,它们使您解释了序言示例的方式确实让我震惊。可以肯定的是,我应该一直这样阅读和思考序言。
theonlygusti

23

首先,功能和逻辑编程之间存在许多共性。也就是说,在一个社区中开发的许多概念也可以在另一个社区中使用。两种范例都是从相当粗糙的实现开始的,并追求纯净。

但是您想知道区别。

因此,我将一方面采用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

在第四个元素之后,该值是不确定的。不过,您可以安全地使用前四个元素:

> take 4 v
[[1,2,3],[2,3],[3],[]]

请注意,对功能程序中的语法进行了严格限制,以避免未定义变量。

在逻辑编程中,变量不需要引用具体值。因此,如果我们要列出3个元素,则可以说:

?- length(Xs,3).
Xs = [_G323, _G326, _G329].

在这个答案中,列表的元素是变量。这些变量的所有可能实例都是有效的解决方案。像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).

稍后,我们可能要求其中的元素Xs都相等。在我写出来之前,我将独自尝试:

?- maplist(=(_),Xs).
Xs = [] ;
Xs = [_G231] ;
Xs = [_G231, _G231] ;
Xs = [_G231, _G231, _G231]  ;
Xs = [_G231, _G231, _G231, _G231] .

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

?- length(Xs,3), Xs = [X|Ys], maplist(dif(X), Ys), maplist(=(_),Xs).
false.

因此,我们在这里显示的是没有第三个元素的列表,其中第一个元素与其他元素不同,所有元素均相等。

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

没有直接的方法可以在函数式编程中获得类似的功能。您可以模拟事物,但是模拟并不完全相同。

缺少什么逻辑编程

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

高阶编程:函数式编程在这里有很长的传统,并开发了丰富的习惯用法。对于Prolog而言,最早的提议可以追溯到1980年代初,但是仍然不是很普遍。至少ISO Prolog现在有适用的同名语call/2, call/3 ...

Lambdas:同样,可以朝那个方向扩展逻辑编程,最著名的系统是Lambda Prolog。最近,还为ISO Prolog开发了lambda 。

类型系统:曾经尝试过像Mercury这样的尝试,但是并没有那么流行。而且,没有一种系统具有可与类型类媲美的功能。

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

在许多领域,功能和逻辑编程或多或少地重叠。例如回溯和lazyness和list解析,懒惰评估和freeze/2when/2block。DCG和monad。我将与其他人讨论这些问题...


1
对于将Haskell与Prolog进行对比,这是一个很好的答案。我不太确定它是否真正成为逻辑和功能范式的核心,而是陷入特定的语言细节中。例如,还有其他一些不懒惰的功能语言,并且有些逻辑编程语言没有未绑定的变量别名,因此无法执行您的示例。水星是我所知道的。它几乎包含了您说的逻辑编程的全部内容,但是您的length / maplist示例无法使用。仍然绝对是逻辑编程。

此外,还有一个小小的更正:Mercury实际上现在在其类型系统中具有类型类。我相信它们对Haskell的类型类具有同等的功能(至少是标准的Haskell;可能不是GHC的所有扩展)。最大的区别是Haskell的类型类在整个标准库中得到了有用的使用,而Mercury的标准库设计大多早于类型类的引入。

@Ben我同意您的看法,并将尽力避免有关Mercury的误导性陈述。自1995年左右以来,我一直在关注水星的发展。长期以来,这种语言似乎会朝着指示的方向发展。特别是在HAL项目中。现在,根据您的评论,我认为水星更像是一种介于两者之间的语言。实际上,更多是在功能方面。但是正如您所说,它仍然绝对是逻辑编程
假的

我想你是说Prolog缺少什么。逻辑编程比Prolog大。
Mostowski Collapse 2014年

18

逻辑编程和功能编程使用不同的“隐喻”进行计算。这通常会影响您考虑如何生成解决方案,并且有时意味着与逻辑程序员相比,功能程序员自然会使用不同的算法。

两者均基于数学基础,可为“纯”代码提供更多好处。不会产生副作用的代码。既有用于执行纯性的范式的语言,也有允许不受限制的副作用的语言,但是从文化上讲,此类语言的程序员仍倾向于重视纯性。

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

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

append [] ys = ys
append (x:xs) ys = x : append xs ys

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

append([], Ys, Ys).
append([X|Xs], Ys, [X|Zs]) :- append(Xs, Ys, Zs).

它们实现相同的算法,甚至基本以相同的方式工作,但是它们“意味着”非常不同的东西。

该功能append定义了将列表附加ys到的结果列表xs。我们想到append视为从两个列表到另一个列表的函数,并且运行时系统被设计为在两个列表上调用函数时计算函数的结果。

逻辑append定义了三个列表之间的关系,如果第三个列表是第一个列表的元素,然后是第二个列表的元素,则为true。我们认为append作为一个谓语是真或假的任何3名中给出列表,运行时系统旨在发现价值,这将使这个谓词真当我们调用它绑定到特定的列表和一些未被结合一些参数。

这使得逻辑的事情append不同的是,你可以用它来计算的列表,从追加一个列表到另一个,但你可以结果用它来计算列表中你需要追加到另一个以最终获得第三列表(或是否不存在这样的列表),计算需要附加另一个列表以获取第三个列表的列表,或者为您提供两个可能的列表,可以将它们附加在一起以获得给定的第三个列表(并探索所有可能的列表)方式)。

尽管您可以彼此做任何事情,但是它们却导致您思考编程任务的方式不同。要在函数式编程中实现某些功能,您需要考虑如何从其他函数调用的结果中产生结果(您可能还必须实现)。要在逻辑编程中实现某些功能,您需要考虑参数之间的关系(某些参数是输入的,有些是输出的,而调用之间不一定是相同的)暗示了期望的关系。


append/3也可以用于生成或测试由单个重复序列组成的所有列表!那就是append(Xs,Xs,XsXs)

1
很好解释。我从来没有这样想过,您提供了新的见识。谢谢。

4

Prolog是一种逻辑语言,可让您免费回溯,这非常引人注目。

详细地说,我想我绝对不是任何一种范例的专家,在我看来,逻辑编程在解决问题上要好得多。因为这正是该语言的功能(例如,在需要回溯时会清楚显示)。


3
@luqui起初只是作为评论,我认为这不是那么愚蠢,即使它没有那么详细:)
m09 2011年

3

我认为区别是:

  • 命令式编程=建模动作
  • 函数编程=建模推理
  • 逻辑编程=建模知识

选择最适合您的想法


0

功能编程:下午6点时亮起。逻辑编程:天黑时亮。


-2

函数式编程和命令式编程之间的区别基于两个概念:

答:-该怎么办?b:-怎么办?

想一想像刚出生的婴儿的计算机,现在您希望该婴儿完成任务(该怎么办?)。现在,如果婴儿比他的功能编程更了解,那么他可以自己知道如何完成该任务。现在,如果那个海湾不知道如何完成这项任务,并且需要程序员的帮助来为该概念制定逻辑,那么这就是命令式编程。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.