过程编程和函数式编程之间有什么区别?
我已经阅读了有关程序化编程和函数式编程的Wikipedia文章,但是我仍然有些困惑。有人可以将其归结为核心吗?
解决方案
回答
在计算机科学中,函数式编程是一种编程范例,将计算视为对数学函数的评估,并避免了状态数据和可变数据。与强调状态变化的过程编程风格相比,它强调函数的应用。
回答
函数式语言(理想情况下)允许我们编写数学函数,即采用n个参数并返回值的函数。如果执行程序,则根据需要对该功能进行逻辑评估。1
另一方面,过程语言执行一系列顺序步骤。 (有一种将顺序逻辑转换为功能逻辑的方法,称为连续传递样式。)
结果,一个纯功能的程序对于输入总是产生相同的值,并且评估的顺序没有明确定义。这意味着很难用纯函数语言来建模诸如用户输入或者随机值之类的不确定值。
1就像这个答案中的所有其他内容一样,这就是一个概括。在需要计算结果而不是在调用结果时按顺序进行计算的属性称为惰性,并且并非所有函数语言实际上普遍都是惰性的,惰性也不限于函数编程。而是,此处给出的描述提供了一个思维框架,以考虑不同的编程风格,这些风格不是截然相反的类别,而是流动的想法。
回答
扩展Konrad的评论:
As a consequence, a purely functional program always yields the same value for an input, and the order of evaluation is not well-defined;
因此,功能代码通常更易于并行化。由于(通常)这些函数没有副作用,并且(通常)仅对它们的参数起作用,因此许多并发问题都消失了。
当我们需要能够证明代码正确时,也可以使用函数式编程。使用过程编程很难做到这一点(使用函数不容易,但是仍然更容易)。
免责声明:我已经多年没有使用函数式编程了,直到最近才再次开始使用它,所以在这里我可能并不完全正确。 :)
回答
我相信过程/功能/目标编程是关于如何解决问题的方法。
第一种样式会按步骤计划所有内容,并通过一次执行一个步骤(一个过程)来解决问题。另一方面,函数式编程将强调分而治之的方法,即将问题分为子问题,然后解决每个子问题(创建一个解决该子问题的函数),并将结果组合为为整个问题创造答案。最后,Objective编程将通过在计算机内部创建一个包含许多对象的微型世界来模仿现实世界,每个对象都具有(某种)独特的特征,并与其他物体进行交互。从这些互动中,结果将显现出来。
每种编程风格都有其自身的优点和缺点。因此,要做一些事情,例如"纯粹的编程"(即,纯程序性的,没有人这样做,这是一种怪异的或者纯粹的功能性或者纯粹的客观性)是非常困难的,即使不是不可能的,除了一些专门设计用于演示编程风格的优势(因此,我们称呼那些喜欢纯粹的人为" weenie":D)。
然后,从这些样式中,我们获得了专门针对每种样式进行优化的编程语言。例如,汇编就是关于程序的。好的,大多数早期语言都是程序语言,不仅像C,Pascal这样的Asm(还有Fortran,我听说)。然后,我们在目标学校都拥有着名的Java(实际上,Java和Cis也在"面向金钱"的课程中,但这是另一个讨论的主题)。目标也是Smalltalk。在功能学校中,我们将拥有"几乎功能性"(有人认为它们是不纯的)Lisp家族和ML家族,还有许多"纯粹功能性"的Haskell,Erlang等。顺便说一下,有许多通用语言,例如Perl,Python ,露比
回答
扩展Konrad的评论:
and the order of evaluation is not well-defined
一些功能语言具有所谓的惰性评估。这意味着直到需要该值时才执行功能。在此之前,函数本身就是传递的东西。
程序语言是步骤1步骤2步骤3 ...如果在步骤2中说添加2 + 2,那么它就正确了。在惰性评估中,我们会说加2 + 2,但是如果从不使用结果,则永远不会进行加法。
回答
程序语言倾向于跟踪状态(使用变量),并倾向于按一系列步骤执行。纯粹的功能语言不会跟踪状态,使用不可变的值,并且倾向于作为一系列依赖项执行。在许多情况下,调用堆栈的状态将保存与程序代码中状态变量中存储的信息等效的信息。
递归是功能样式编程的经典示例。
回答
基本上这两种风格,就像阴和阳。一个是有组织的,而另一个则是混乱的。在某些情况下,函数式编程是显而易见的选择,而在其他情况下,过程式编程是更好的选择。这就是为什么最近至少有两种新版本推出了包含两种编程风格的语言的原因。 (Perl6和D2)
- 例程的输出并不总是与输入直接相关。
- 一切都以特定顺序完成。
- 执行例程可能会有副作用。
- 倾向于强调以线性方式实施解决方案。
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; }
第2天
int factorial( int n ){ int result = 1; for( ; n > 0 ; n-- ){ result *= n; } return result; }
- 经常递归。
- 对于给定的输入,始终返回相同的输出。
- 评估顺序通常是不确定的。
- 必须是无状态的。也就是说,任何操作都不会产生副作用。
- 非常适合并行执行
- 倾向于强调分而治之的方法。
- 可能具有"延迟评估"功能。
哈斯克尔
(从Wikipedia复制);
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 }
第2天
pure int factorial( invariant int n ){ if( n <= 1 ){ return 1; }else{ return n * factorial( n-1 ); } }
实际上,阶乘是一个常见的示例,它展示了以与创建子例程相同的方式在Perl 6中创建新运算符是多么容易。此功能已根深蒂固地融入了Perl 6,以至于Rakudo实现中的大多数运算符都是以这种方式定义的。它还允许我们将自己的多个候选者添加到现有的运算符中。
sub postfix:< ! > ( UInt:D $n --> UInt ) is tighter(&infix:<*>) { [*] 2 .. $n } say 5!; # 120?
此示例还显示了范围创建(2 .. $ n
)和列表缩减元运算符([?OPERATOR?]?LIST
)与数字中缀乘法运算符的组合。 (*
)
它还显示我们可以在签名中放入"-> UInt",而不是在其后添加" returnsUInt"。
(我们可以从以" 2"开始范围开始,因为在不带任何参数的情况下,乘以" operator"将返回" 1")
回答
我在这里没有真正强调过的一件事是,像Haskell这样的现代功能语言实际上更多地是关于用于流控制的一流功能,而不是显式递归。我们无需像上面一样在Haskell中递归定义阶乘。我觉得像
fac n = foldr (*) 1 [1..n]
是一个完美的习惯用法,在本质上更类似于使用循环而不是使用显式递归。
回答
康拉德说:
As a consequence, a purely functional program always yields the same value for an input, and the order of evaluation is not well-defined; which means that uncertain values like user input or random values are hard to model in purely functional languages.
在纯功能程序中,评估的顺序可能很难(尤其是懒惰)甚至不重要,但我认为说它的定义不明确,听起来好像我们无法判断程序是否在运行上班!
也许更好的解释是功能程序中的控制流基于何时需要函数参数的值。关于这一点的好消息是,在编写良好的程序中,状态变得很明确:每个函数都将其输入作为参数列出,而不是任意改变全局状态。因此,在某种程度上,更容易一次就一个功能推断出评估顺序。每个函数都可以忽略其余部分,而将精力集中在需要做的事情上。组合使用时,可以保证功能与单独运行时的功能相同[1]。
... uncertain values like user input or random values are hard to model in purely functional languages.
在纯功能程序中,输入问题的解决方案是使用足够强大的抽象将命令性语言嵌入DSL。在命令式(或者非纯函数式)语言中,这不是必需的,因为我们可以"欺骗"并隐式传递状态,并且评估的顺序是明确的(无论我们是否喜欢)。由于这种"欺骗"和对所有函数的所有参数的强制求值,使用命令式语言1)我们失去了创建自己的控制流机制(没有宏)的能力,2)代码本质上不是线程安全和/或者可并行化的默认情况下,3)并实现诸如undo(时间旅行)之类的工作需要认真的工作(当务之急,程序员必须存储一个配方以取回旧的值!),而纯函数式编程可以为我们提供所有这些东西,还有更多忘记了"免费"。
我希望这听起来不像是狂热,我只是想补充一些观点。命令式编程,尤其是C3.0等强大语言中的混合范例编程,仍然是完成任务的完全有效方法,没有灵丹妙药。
[1] ...可能在内存使用方面除外(参见Haskell中的foldl和foldl')。
回答
@Creighton:
在Haskell中,有一个名为product的库函数:
prouduct list = foldr 1 (*) list
或者简单地:
product = foldr 1 (*)
所以"惯用"阶乘
fac n = foldr 1 (*) [1..n]
简直是
fac n = product [1..n]
回答
程序性编程将语句和条件构造的序列分为称为过程的单独块,这些块通过(非功能性)值的参数进行参数化。
除了函数是一等值之外,函数式编程是相同的,因此可以将它们作为参数传递给其他函数,并作为函数调用的结果返回。
注意,在这种解释中,函数式编程是过程编程的概括。但是,少数人将"函数式编程"解释为无副作用,这是完全不同的,但与除Haskell之外的所有主要函数式语言无关。
回答
如果有机会,我建议我们获得一份Lisp / Scheme副本,并在其中进行一些项目。几十年前,在Lisp中表达了最近成为潮流的大多数想法:函数式编程,延续(作为闭包),垃圾回收,甚至XML。
因此,这是抢占所有这些当前想法以及其他一些想法(例如符号计算)的好方法。
我们应该知道函数式编程有什么用处,而哪些不好。这并不适合所有事情。一些问题最好用副作用来表示,其中相同的问题根据提出的时间而给出不同的答案。