我读过许多文章试图理解功能和逻辑编程之间的区别,但是到目前为止,我唯一能得出的结论就是逻辑编程通过数学表达式定义程序。但是这种事情与逻辑编程无关。
我真的很希望能对函数式编程与逻辑编程之间的区别有所了解。
我读过许多文章试图理解功能和逻辑编程之间的区别,但是到目前为止,我唯一能得出的结论就是逻辑编程通过数学表达式定义程序。但是这种事情与逻辑编程无关。
我真的很希望能对函数式编程与逻辑编程之间的区别有所了解。
Answers:
我不会说逻辑编程是通过数学表达式定义程序的。听起来更像是函数式编程。逻辑编程使用逻辑表达式(当然,逻辑最终是数学)。
在我看来,功能和逻辑编程之间的主要区别是“构建块”:功能编程使用函数,而逻辑编程使用谓词。谓词不是函数;它没有返回值。取决于参数的值,它可以是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允许您以实用甚至命令式的风格编写(结果令人吃惊)。
简而言之:
在函数式编程中,您的程序是一组函数定义。每个函数的返回值都将作为数学表达式求值,可能使用传递的参数和其他定义的函数。例如,您可以定义一个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都是一个好的答案。
factorial(X,3)
会产生实例化错误。因此,与功能性程序的唯一区别是,在Prolog中,您会动态地得到该错误。但理想情况下,您也可以证明上述查询没有解决方案。实际上,像library(clpfd)这样的约束条件都能为您提供正确的答案。请参阅n_factorial/2
。有两种实现:天真的和更有效的实现。
首先,功能和逻辑编程之间存在许多共性。也就是说,在一个社区中开发的许多概念也可以在另一个社区中使用。两种范例都是从相当粗糙的实现开始的,并追求纯净。
但是您想知道区别。
因此,我将一方面采用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系统的库提供,最主要的是CLPFD和CHR。
没有直接的方法可以在函数式编程中获得类似的功能。您可以模拟事物,但是模拟并不完全相同。
但是逻辑编程中缺少许多东西,使函数式编程变得如此有趣。特别是:
高阶编程:函数式编程在这里有很长的传统,并开发了丰富的习惯用法。对于Prolog而言,最早的提议可以追溯到1980年代初,但是仍然不是很普遍。至少ISO Prolog现在有适用的同名语call/2, call/3 ...
。
Lambdas:同样,可以朝那个方向扩展逻辑编程,最著名的系统是Lambda Prolog。最近,还为ISO Prolog开发了lambda 。
类型系统:曾经尝试过像Mercury这样的尝试,但是并没有那么流行。而且,没有一种系统具有可与类型类媲美的功能。
纯度:Haskell是完全纯净的,一个函数Integer-> Integer是一个函数。周围没有精美的印刷品。而且您仍然可以执行副作用。可比的方法发展非常缓慢。
在许多领域,功能和逻辑编程或多或少地重叠。例如回溯和lazyness和list解析,懒惰评估和freeze/2
,when/2
,block
。DCG和monad。我将与其他人讨论这些问题...
逻辑编程和功能编程使用不同的“隐喻”进行计算。这通常会影响您考虑如何生成解决方案,并且有时意味着与逻辑程序员相比,功能程序员自然会使用不同的算法。
两者均基于数学基础,可为“纯”代码提供更多好处。不会产生副作用的代码。既有用于执行纯性的范式的语言,也有允许不受限制的副作用的语言,但是从文化上讲,此类语言的程序员仍倾向于重视纯性。
我将考虑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)
!
我认为区别是:
选择最适合您的想法
函数式编程和命令式编程之间的区别基于两个概念:
答:-该怎么办?b:-怎么办?
想一想像刚出生的婴儿的计算机,现在您希望该婴儿完成任务(该怎么办?)。现在,如果婴儿比他的功能编程更了解,那么他可以自己知道如何完成该任务。现在,如果那个海湾不知道如何完成这项任务,并且需要程序员的帮助来为该概念制定逻辑,那么这就是命令式编程。