逻辑编程与函数式编程的区别

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

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

34469 次浏览

Prolog 作为一种逻辑语言,可以免费回溯,这一点非常明显。

为了详细说明,我确切地说,我在任何范例中都不是专家,在我看来,逻辑编程在解决问题时要好得多。因为这正是语言所做的(例如,当需要回溯时,这一点就显得很清楚)。

简而言之:

在函数式编程中,程序是一组函数定义。每个函数的返回值作为数学表达式计算,可能使用传递的参数和其他已定义的函数。例如,您可以定义一个 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”谓词,只要第二个参数是第一个阶乘,它就会存在:

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

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

I wouldn't say that logic programming defines programs through mathematical expressions; that sounds more like functional programming. Logic programming uses logic expressions (well, eventually logic is math).

在我看来,函数式编程和逻辑编程的主要区别在于“构建块”: 函数式编程使用函数,而逻辑编程使用谓词。谓词不是函数; 它没有返回值。根据它的参数的值,它可能是 true 或 false; 如果一些值没有定义,它将尝试找到使谓词为 true 的值。

Prolog 特别使用一种名为 号角条款的特殊形式的逻辑子句,它属于一阶逻辑; Hilog 使用高阶逻辑的子句。

当你写一个序言谓词时,你是在定义一个圆号子句: foo :- bar1, bar2, bar3.表示,如果 bar1、 bar2和 bar3为真,foo 为真。 注意,我没有说 if 和 only if; 可以为一个谓词使用多个子句:

foo:-
bar1.
foo:-
bar2.

意味着如果 bar1为真,或者 bar2为真,foo 为真

Some say that logic programming is a superset of functional programming since each function could be expressed as a predicate:

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

可以写成

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

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

Another difference between logic and functional is backtracking. In functional programming once you enter the body of the function you cannot fail and move to the next definition. For example you can write

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

or even use guards:

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

I do not think that it is impossible for a functional language to implement something similar (but I haven't used such a language).

总而言之,尽管这两个范例都被认为是声明性的,但它们是完全不同的; 差异如此之大,以至于比较它们感觉就像比较函数式和命令式样式。我建议尝试一下逻辑编程,这应该是一个令人难以置信的经历。然而,您应该尝试理解其哲学,而不是简单地编写程序; Prolog 允许您以函数式甚至命令式的方式编写程序(结果非常糟糕)。

首先,函数式编程和逻辑编程有很多共同之处。也就是说,在一个社区中形成的许多概念也可以在另一个社区中使用。这两种范例都是从相当粗糙的实现开始的,并努力实现纯粹性。

但你想知道其中的区别。

所以我将 Haskell 放在一边,Prolog 放在另一边。实际上,当前所有的 Prolog 系统都提供某种约束,比如 B、 Ciao、 ECLiPse、 GNU、 IF、 Scryer、 SICStus、 SWI、 YAP、 XSB。出于讨论的目的,我将使用一个非常简单的约束 dif/2,它意味着不等式,甚至在第一个 Prolog 实现中就已经存在了——所以我不会使用比它更高级的东西。

函数式编程所缺乏的

最根本的区别在于变量的概念。在函数式编程中,变量表示具体的值。此值不能完全定义,但只有那些已定义的部分可以在计算中使用。想想哈斯克尔:

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

After the 4th element, the value is undefined. Nevertheless, you can use the first 4 elements safely:

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

注意,函数式程序中的语法受到了巧妙的限制,以避免变量未定义。

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

?- length(Xs,3).
Xs = [_A,_B,_C].

在这个答案中,列表的元素是变量。这些变量的 全部可能的实例是有效的解决方案。就像 Xs = [1,2,3]。现在,让我们假设第一个元素应该与其余的元素不同:

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

稍后,我们可能会要求 Xs中的所有元素都相等。在我把它写出来之前,我会自己试一试:

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

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

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

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

这种通用性允许开发几种约束语言,这些语言作为 Prolog 系统的库提供,其中最突出的是 消防处人权中心

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

缺乏逻辑编程

但是在逻辑编程中有很多缺陷使函数式编程如此有趣,特别是:

高阶编程: 函数式编程在这方面有着非常悠久的传统,并且已经形成了一套丰富的习惯用法。对于 Prolog,第一个提案可以追溯到20世纪80年代早期,但是它仍然不是很普遍。至少 ISO Prolog 现在有了应用的同源物 call/2, call/3 ...

Lambdas: 同样,在这个方向上扩展逻辑编程是可能的,最突出的系统是 Lambda Prolog。最近,lambdas 也开发了 国际标准化组织序言

类型系统: 已经有过尝试,像水星,但它没有抓住那么多。而且没有一个系统的功能可以与类型类相媲美。

Purity: Haskell is entirely pure, a function Integer -> Integer is a function. No fine print lurking around. And still you can perform side effects. Comparable approaches are very slowly evolving.

函数式编程和逻辑编程在许多领域或多或少存在重叠。例如回溯和懒惰和列表理解,懒惰评估和 freeze/2when/2block。DCG 和单子。我会把讨论这些问题留给其他人..。

逻辑编程和函数式编程使用不同的“比喻”进行计算。这通常会影响您对产生解决方案的思考方式,并且有时意味着函数式程序员比逻辑程序员更容易产生不同的算法。

两者都基于数学基础,为“纯”代码提供了更多好处; 代码不会带来副作用。这两种范例都有强制纯度的语言,也有允许不受限制的副作用的语言,但是从文化上来说,这些语言的程序员仍然倾向于重视纯度。

我将考虑 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是一个从两个列表到另一个列表的函数,运行时系统的设计目的是在两个列表上调用该函数时计算结果。

The logical append defines a relationship between three lists, which is true if the third list is the elements of the first list followed by the elements of the second list. We think of append as a 断言 that is either true or false for any 3 given lists, and the runtime system is designed to find values that will make this predicate true when we invoke it with some arguments bound to specific lists and some left unbound.

逻辑 append的不同之处在于,你可以使用它来计算将一个列表附加到另一个列表所得到的列表,但是你可以使用它来计算需要附加到另一个列表的末尾才能得到第三个列表(或者是否不存在这样的列表) ,使用 or来计算需要附加另一个列表才能得到第三个列表的列表,使用 or来给出两个可以附加在一起才能得到给定的第三个列表的可能列表(并探索所有可能的方法)。

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

我认为不同之处在于:

  • imperative programming=modelling actions
  • 函数编程 = 建模推理
  • 逻辑程序设计 = 建模知识

选择最适合你思想的

函数式编程: 下午6点,灯亮着。 逻辑编程: 黑暗中,灯光闪耀。