过程式编程和函数式编程有什么区别?

我已经阅读了维基百科的程序编程和函数式编程文章 ,但我仍然有点困惑。 有人可以把它归结为核心?

一个函数式语言(理想情况下)允许你写一个math函数,即一个函数,它接受n个参数并返回一个值。 如果程序被执行,则根据需要对该function进行逻辑评估。 1

另一方面,程序语言执行一系列连续的步骤。 (有一种将顺序逻辑转换成function逻辑的方法,称为延续传球风格 。

因此,一个纯粹的function性程序对于一个input总是产生相同的价值 ,而评价的顺序并不明确; 这意味着像用户input或随机值这样的不确定值很难在纯粹的function语言中build模。


1在这个答案中的所有其他内容,这是一个泛化。 当计算结果被需要而不是顺序地被称为“懒惰”时,评估计算的性质并不是所有的函数式语言实际上都是普遍懒惰的,懒惰也不限于函数式编程。 相反,这里给出的描述提供了一个“思维框架”来思考不同的编程风格,这些编程风格并不是截然不同的类别,而是stream畅的想法。

基本上这两种风格,就像阴阳一样。 一个是有组织的,另一个是混乱的。 有些情况下,函数式编程是显而易见的select,其他情况是程序式编程是更好的select。 这就是为什么至less有两种语言最近出现了一个新的版本,它包含了两种编程风格。 ( Perl 6和D 2 )

程序:

  • 例程的输出并不总是与input直接相关。
  • 一切都按照特定的顺序完成。
  • 例程的执行可能有副作用。
  • 倾向于强调以线性方式实施解决scheme。

Perl 6

sub factorial ( UInt:D $n is copy ) returns UInt { # modify "outside" state state $call-count++; # in this case it is rather pointless as # it can't even be accessed from outside my $result = 1; loop ( ; $n > 0 ; $n-- ){ $result *= $n; } return $result; } 

D 2

 int factorial( int n ){ int result = 1; for( ; n > 0 ; n-- ){ result *= n; } return result; } 

function:

  • 经常recursion。
  • 始终返回给定input的相同输出。
  • 评估顺序通常是不确定的。
  • 必须是无国籍的。 即没有手术可以有副作用。
  • 非常适合并行执行
  • 倾向于强调分而治之的方法。
  • 可能有懒惰评估的特点。

哈斯克尔

(从维基百科复制);

 fac :: Integer -> Integer fac 0 = 1 fac n | n > 0 = n * fac (n-1) 

或者在一行中:

 fac n = if n > 0 then n * fac (n-1) else 1 

Perl 6

 proto sub factorial ( UInt:D $n ) returns UInt {*} multi sub factorial ( 0 ) { 1 } multi sub factorial ( $n ) { $n * samewith $n-1 } # { $n * factorial $n-1 } 

D 2

 pure int factorial( invariant int n ){ if( n <= 1 ){ return 1; }else{ return n * factorial( n-1 ); } } 

边注:

因式分析实际上是一个常见的例子,显示在Perl 6中创build新的操作符是多么容易,就像创build一个子例程一样。 这个特性如此深入到Perl 6中,Rakudo实现中的大多数操作符都是这样定义的。 它还允许您将自己的多候选人添加到现有的运营商。

 sub postfix:< ! > ( UInt:D $n --> UInt ) is tighter(&infix:<*>) { [*] 2 .. $n } say 5!; # 120␤ 

此示例还显示范围创build( 2..$n )和列表缩减元运算符( [ OPERATOR ] LIST )与数字中缀乘法运算符相结合。 ( *
它也表明你可以把--> UInt放在签名中,而不是在它之后returns UInt

(你可以用2开始范围,因为乘法运算符在没有任何参数的情况下调用时会返回1

在计算机科学中,函数式编程是一种将计算看作是math函数的评估并避免状态和可变数据的编程范例。 它强调function的应用,与强调状态变化的程序化编程风格形成鲜明对比。

我从来没有见过其他地方给出过这个定义,但我想这总结了这里给出的不同之处:

函数式编程着重于expression式

程序性程序devise侧重于陈述

expression式有价值。 一个function程序是一个expression谁的价值是计算机执行的一系列指令。

语句没有值,而是修改一些概念机器的状态。

在一个纯粹的函数式语言中,不存在任何语句,因为没有办法操纵状态(它们可能仍然有一个名为“语句”的语法结构,但是除非它操纵状态,我不会把它称为这个意义上的语句)。 在纯粹的程序语言中,不会有expression式,所有的东西都是一个操纵机器状态的指令。

Haskell将会是一个纯粹function语言的例子,因为没有办法操纵状态。 机器代码将是纯程序语言的一个例子,因为程序中的所有内容都是操纵机器寄存器和内存状态的语句。

令人困惑的是,绝大多数编程语言都包含expression式和语句,使您可以混合使用范例。 语言可以分为更多的function或更多的程序,基于他们鼓励使用陈述与expression的程度。

例如,C将比COBOL更具function,因为函数调用是一个expression式,而在COBOL中调用子程序是一个语句(操作共享variables的状态并且不返回值)。 Python会比C更具function性,因为它允许您使用短路评估(test && path1 || path2而不是if语句)将条件逻辑expression为expression式。 Scheme比Python更具function性,因为scheme中的所有内容都是expression式。

你仍然可以用一种鼓励程序性范式的语言写作,反之亦然。 写一个不受语言鼓励的范式是更难和/或更尴尬的。

我相信程序性/function性/客观性程序是关于如何解决问题的。

第一种方式是将所有步骤都计划好,并通过一次实施一个步骤(一个过程)来解决问题。 另一方面,函数式编程将强调分而治之的方法,将问题分解为子问题,然后解决每个子问题(创build一个解决子问题的函数),并将结果合并到为整个问题创造答案。 最后,客观编程将通过在计算机内创build一个具有许多对象的小世界来模拟现实世界,每个对象都具有(某种)独特的特征,并与其他对象交互。 从这些相互作用中,结果会出现。

每种编程风格都有自己的优点和缺点。 因此,做一些诸如“纯粹的程序devise”(纯粹是程序性的,顺便说一句,这是一种奇怪的,或纯粹的function性的或纯粹客观的),是非常困难的,即使不是不可能的,除了一些基本的问题旨在展示编程风格的优势(因此,我们称之为纯粹的“weenie”:D)。

然后,从这些风格,我们有编程语言,旨在为每种风格进行优化。 例如,大会是关于程序的。 好吧,大多数早期语言都是程序化的,不仅是Asm,还有C,Pascal(和Fortran,我听说过)。 那么,我们在目标学校都有名的Java(实际上,Java和C#也是在一个叫做“金钱导向”的类中,但这是另外一个讨论的话题)。 Smalltalk也是客观的。 在function性学校,我们会有“近乎function”(有些认为它们是不纯的)Lisp家族和ML家族以及许多“纯粹function性”的Haskell,Erlang等等。顺便说一下,Perl,Python ,Ruby。

为了扩大Konrad的评论:

因此,一个纯粹的function性程序对于一个input总是产生相同的价值,而评价的顺序并不明确;

因此,function代码通常更容易并行化。 由于这些function通常没有副作用,而且他们(通常)只是对他们的论点采取行动,所以许多并发问题就消失了。

当你需要能够certificate你的代码是正确的,函数式编程也被使用。 程序编程(用function不容易,但更容易)要困难得多。

免责声明:我多年来一直没有使用函数式编程,只是最近才开始重新查看,所以在这里我可能不完全正确。 🙂

有一件事我没有看到真正强调的是现代函数式语言如Haskell对于stream控制的第一类函数比显式recursion更多。 您不需要像上面那样在Haskell中recursion地定义阶乘。 我觉得像

 fac n = foldr (*) 1 [1..n] 

是一个完美的惯用的构造,使用循环的精神比使用显式recursion的要紧得多。

康拉德说:

因此,一个纯粹的function性程序对于一个input总是产生相同的价值,而评价的顺序并不明确; 这意味着像用户input或随机值这样的不确定值很难在纯粹的function语言中build模。

在一个纯function程序中评估的顺序可能很难(尤其是懒惰),甚至是不重要的,但是我认为说这个没有很好的定义,听起来就像你不知道你的程序是否正在进行一起工作!

也许更好的解释是,函数式程序中的控制stream是基于需要函数参数值的时候。 好的事情在于,在写得很好的程序中,状态变得明确:每个函数都将其input列为参数,而不是任意的全局状态。 所以在某个层面上, 关于一个function的评价顺序一次就容易推理了 。 每个function可以忽略宇宙的其余部分,并专注于它需要做的事情。 当组合起来的时候,function保证和它们孤立地一样工作[1]。

…用户input或随机值等不确定的值很难在纯function语言中build模。

纯function程序中的input问题的解决scheme是使用足够强大的抽象将命令式语言embedded为DSL 。 在命令性(或非纯function性)语言中,这是不需要的,因为您可以“隐藏”并隐式传递状态,并且评估的顺序是明确的(无论您是否喜欢)。 由于这种“欺骗”和强制评估所有参数给每个函数,在命令式语言中,1)您失去了创build自己的控制stream机制(无macros)的能力,2)代码本质上不是线程安全的和/或可并行默认情况下 ,3)和实现像撤消(时间旅行)这样的事情需要仔细的工作(命令式程序员必须存储一个获得旧值的方法!),而纯函数式编程为您带来了所有这些东西 – 还有一些我可能已经忘记 – “免费”。

我希望这听起来不像是狂热,我只是想补充一些观点。 命令式编程,特别是像C#3.0这样强大的语言混合范式编程,仍然是完成任务的完全有效的方式, 没有银弹 。

[1] …除了可能的方面尊重内存使用(参考在哈斯克尔foldl和foldl')。

程序语言倾向于跟踪状态(使用variables)并倾向于按照一系列步骤执行。 纯粹的函数式语言不会跟踪状态,使用不可变的值,并倾向于作为一系列的依赖项来执行。 在许多情况下,调用堆栈的状态将保存与过程代码中将存储在状态variables中的信息相同的信息。

recursion是function风格编程的经典例子。

为了扩大Konrad的评论:

评价顺序不明确

一些function语言有所谓的懒惰评估。 这意味着一个函数不会被执行,直到需要的值。 直到那个时候,函数本身就是传递的。

程序语言是步骤1步骤2步骤3 …如果在步骤2中说,添加2 + 2,它是正确的。 在懒惰的评估你会说加2 + 2,但如果结果从来没有使用过,它从来没有加法。

如果你有机会的话,我会build议你得到Lisp / Scheme的副本,并在其中做一些项目。 大多数最近成为带头人的想法在几十年前的Lisp中都有expression:函数式编程,延续(作为闭包),垃圾收集,甚至是XML。

所以这将是一个很好的方式来在所有这些当前的想法上取得先机,另外还有一些像符号计算一样。

你应该知道什么函数编程是好的,什么是不好的。 这对一切都不好。 有些问题最好用副作用来expression,同样的问题根据问题的不同提供不同的答案。

函数式编程与全局variables未被使用的过程式编程相同。

@Creighton:

在Haskell中有一个叫做product的库函数:

 prouduct list = foldr 1 (*) list 

或者干脆:

 product = foldr 1 (*) 

所以“惯用”因数

 fac n = foldr 1 (*) [1..n] 

简直就是

 fac n = product [1..n] 

程序化编程将语句序列和条件结构划分为独立的块,称为过程参数化(非function性)参数。

函数式编程除了函数是first-class值之外都是一样的,所以它们可以作为parameter passing给其他函数,并作为函数调用的结果返回。

请注意,函数式编程是这种解释中过程式编程的泛化。 然而,less数人将“函数式编程”解释为无副作用,这与Haskell以外的所有主要函数式语言都有很大不同,但是并不相同。