我已经阅读过程序编程和函数式编程的维基百科文章,但我仍然有点困惑.有人可以把它归结为核心吗?
函数式语言(理想情况下)允许您编写数学函数,即带有n个参数并返回值的函数.如果执行该程序,则根据需要对该功能进行逻辑评估.1
另一方面,过程语言执行一系列顺序步骤.(有一种方法可以将顺序逻辑转换为称为延续传递方式的功能逻辑.)
因此,纯函数式程序总是为输入产生相同的值,并且评估的顺序没有明确定义; 这意味着用户输入或随机值等不确定值很难用纯函数式语言进行建模.
1作为这个答案中的其他内容,这是一个概括.在需要其结果时计算计算的属性而不是顺序被称为"懒惰"的属性被称为"懒惰",并且并非所有函数语言实际上都是普遍的懒惰,懒惰也不限于函数式编程.相反,这里给出的描述提供了一个"心理框架"来思考不同的编程风格,这些风格不是截然不同的相反类别,而是流畅的想法.
基本上这两种风格,都喜欢阴阳.一个是有组织的,而另一个是混乱的.有些情况下功能编程是明显的选择,其他情况是程序编程是更好的选择.这就是为什么至少有两种语言最近推出了新版本,它包含了两种编程风格.(Perl 6和D 2)
程序:
例程的输出并不总是与输入直接相关.
一切都按照特定的顺序完成.
执行例程可能会产生副作用.
倾向于强调以线性方式实施解决方案.
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;
}
int factorial( int n ){
int result = 1;
for( ; n > 0 ; n-- ){
result *= n;
}
return result;
}
通常是递归的.
始终为给定输入返回相同的输出.
评估顺序通常是不确定的.
必须是无国籍的.即没有手术会产生副作用.
非常适合并行执行
倾向于强调分而治之的方法.
可能具有懒惰评估功能.
(从维基百科复制);
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
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 }
pure int factorial( invariant int n ){
if( n <= 1 ){
return 1;
}else{
return n * factorial( n-1 );
}
}
Factorial实际上是一个常见的例子,表明在Perl 6中创建新运算符是多么容易,就像创建子例程一样.这个特性在Perl 6中根深蒂固,Rakudo实现中的大多数操作符都是以这种方式定义的.它还允许您将自己的多候选项添加到现有运算符.
sub postfix:< ! > ( UInt:D $n --> UInt )
is tighter(&infix:<*>)
{ [*] 2 .. $n }
say 5!; # 120?
此示例还显示范围creation(2..$n
)和列表缩减元运算符([ OPERATOR ] LIST
)与数字中缀乘法运算符的组合.(*
)
它还表明你可以放入--> UInt
签名而不是签名returns UInt
.
(您可以通过启动范围来逃避,2
因为1
当没有任何参数调用时,乘法"运算符"将返回)
我从未在其他地方看到这个定义,但我认为这总结了这里给出的差异:
功能编程侧重于表达式
程序编程侧重于语句
表达式具有价值.功能程序是一个表达式,其值是计算机执行的一系列指令.
语句没有值,而是修改某个概念机器的状态.
在一个纯粹的函数式语言中,没有语句,因为没有办法操纵状态(它们可能仍然有一个名为"statement"的语法结构,但除非它操纵状态,否则我不会在这个意义上称它为语句).在纯粹的过程语言中,没有表达式,一切都是操纵机器状态的指令.
Haskell将是一个纯函数式语言的例子,因为没有办法操纵状态.机器代码将是纯过程语言的一个示例,因为程序中的所有内容都是一个操作寄存器状态和机器内存的语句.
混乱的是,绝大多数的编程语言包含两个表达式和语句,使您可以混合范式.语言可以根据他们鼓励使用语句与表达的程度来分类为更多功能或更多程序.
例如,C将比COBOL更有用,因为函数调用是一个表达式,而在COBOL中调用子程序是一个语句(它操纵共享变量的状态而不返回值).Python将比C更具功能性,因为它允许您使用短路评估将条件逻辑表达为表达式(test && path1 || path2而不是if语句).Scheme比Python更有用,因为scheme中的所有东西都是表达式.
您仍然可以使用鼓励程序范例的语言编写功能样式,反之亦然.写一个不受语言鼓励的范式更难和/或更尴尬.
在计算机科学中,函数式编程是一种编程范式,它将计算视为数学函数的评估,并避免状态和可变数据.它强调功能的应用,与强调状态变化的程序编程风格形成对比.
我认为程序/功能/目标编程是关于如何处理问题的.
第一种风格将计划所有步骤,并通过一次实施一个步骤(一个过程)来解决问题.另一方面,函数式编程将强调分而治之的方法,其中问题被分为子问题,然后解决每个子问题(创建解决该子问题的函数)并将结果合并到为整个问题创造答案.最后,目标编程将模拟真实世界,在计算机内部创建一个具有许多对象的迷你世界,每个对象都具有(某种程度上)独特的特征,并与其他对象进行交互.从这些相互作用中,结果将会出现.
每种编程风格都有自己的优点和缺点.因此,做一些诸如"纯编程"(即纯粹的程序性 - 没有人这样做,顺便说一下,有点奇怪 - 或纯粹的功能或纯粹的客观)是非常困难的,如果不是不可能的话,除了一些特殊的基本问题旨在展示编程风格的优势(因此,我们称那些喜欢纯粹的人为"weenie":D).
然后,从这些样式中,我们有编程语言,旨在针对每种样式进行优化.例如,汇编就是程序性的.好吧,大多数早期语言都是程序性的,不仅仅是Asm,比如C,Pascal,(和Fortran,我听说过).然后,我们在客观学校中拥有所有着名的Java(实际上,Java和C#也属于一个名为"以金钱为导向"的课程,但这是另一个讨论的主题).Smalltalk也是客观的.在功能学校,我们会有"近乎功能性"(有些人认为它们是不纯的)Lisp家族和ML家族以及许多"纯粹功能性"的Haskell,Erlang等.顺便说一下,有许多通用语言,如Perl,Python ,Ruby.
num = 1 def function_to_add_one(num): num += 1 return num function_to_add_one(num) function_to_add_one(num) function_to_add_one(num) function_to_add_one(num) function_to_add_one(num) #Final Output: 2
num = 1 def procedure_to_add_one(): global num num += 1 return num procedure_to_add_one() procedure_to_add_one() procedure_to_add_one() procedure_to_add_one() procedure_to_add_one() #Final Output: 6
function_to_add_one
是一个功能
procedure_to_add_one
是一个过程
即使您运行该函数五次,每次也会返回2
如果您运行该过程五次,则在第五次运行结束时将给您6。
扩展康拉德的评论:
因此,纯函数式程序总是为输入产生相同的值,并且评估的顺序没有明确定义;
因此,功能代码通常更容易并行化.由于(通常)函数没有副作用,并且它们(通常)只是对它们的参数起作用,因此很多并发问题都会消失.
当您需要能够证明您的代码是正确的时,也会使用函数式编程.这对于程序编程来说要困难得多(功能不容易,但更容易).
免责声明:我多年没有使用过函数式编程,而且最近才开始再次查看它,所以我在这里可能不完全正确.:)
我在这里没有看到的一件事是,现代函数语言(如Haskell)在流控制的第一类函数上比在显式递归中更多.您不需要在Haskell中递归地定义阶乘,如上所述.我觉得有点像
fac n = foldr (*) 1 [1..n]
是一个完美的惯用结构,在使用循环方面比使用显式递归更接近精神.
函数式编程与不使用全局变量的过程式编程相同.
过程语言倾向于跟踪状态(使用变量)并倾向于作为一系列步骤执行.纯函数式语言不跟踪状态,使用不可变值,并倾向于作为一系列依赖项执行.在许多情况下,调用堆栈的状态将保存与过程代码中的状态变量中存储的信息等效的信息.
递归是功能样式编程的典型例子.
康拉德说:
因此,纯函数式程序总是为输入产生相同的值,并且评估的顺序没有明确定义; 这意味着用户输入或随机值等不确定值很难用纯函数式语言进行建模.
纯粹功能性程序中的评估顺序可能很难(呃)推理(特别是懒惰)甚至不重要但我认为说它没有明确定义会让你听不清楚你的程序是否正常运行工作!
或许更好的解释是功能程序中的控制流程基于何时需要函数参数的值.关于这一点的好事,在编写良好的程序中,状态变得明确:每个函数将其输入列为参数而不是任意地改变全局状态.因此,在某种程度上,一次就一个函数更容易推理评估顺序.每个函数都可以忽略宇宙的其余部分,并专注于它需要做的事情.结合使用时,保证功能与隔离时的功能相同[1].
...用户输入或随机值等不确定值很难用纯函数语言建模.
纯功能程序中输入问题的解决方案是使用足够强大的抽象将命令式语言嵌入到DSL中.在命令式(或非纯函数式)语言中,这不是必需的,因为您可以"欺骗"并隐式传递状态,并且评估的顺序是明确的(无论您是否喜欢).由于这种"作弊"并强制评估每个函数的所有参数,在命令式语言1)你失去了创建自己的控制流机制(没有宏)的能力,2)代码本身并不是线程安全的和/或可并行化的默认情况下,3)并实现像撤消(时间旅行)之类的东西需要仔细的工作(命令式程序员必须存储一个用于获取旧值的配方!),而纯函数编程会购买所有这些东西 - 还有一些我可能忘了 - "免费".
我希望这听起来不像狂热,我只想添加一些观点.命令式编程,尤其是C#3.0等强大语言中的混合范式编程仍然是完成任务的完全有效的方法,并且没有灵丹妙药.
[1] ...除了可能尊重内存使用(参见Haskell中的foldl和foldl').
扩展康拉德的评论:
并且评估顺序没有明确定义
一些函数式语言具有所谓的Lazy Evaluation.这意味着在需要值之前不会执行函数.在此之前,函数本身就是传递的东西.
程序语言是步骤1步骤2步骤3 ...如果在步骤2中你说加2 + 2,那么它就是正确的.在延迟评估中,您会说添加2 + 2,但如果结果从未使用过,则它永远不会添加.