真正理解程序和function之间的区别
我真的很难理解程序和函数编程范例之间的区别。
以下是维基百科关于函数式编程的前两段:
在计算机科学中,函数式编程是一种将计算看作是math函数的评估并避免状态和可变数据的编程范例。 它强调function的应用,与强调状态变化的命令式编程风格形成鲜明对比。 函数式编程的根源在于lambda演算,这是20世纪30年代为了研究函数定义,函数应用和recursion而开发的一个正式系统。 许多函数式编程语言可以被看作是lambda演算的详细描述。
在实践中,math函数与命令式编程中使用的“函数”概念之间的区别在于,命令式函数会产生副作用,从而改变程序状态的值。 正因为如此,它们缺乏参考透明性,即相同的语言expression式可能会在不同的时间导致不同的值,这取决于正在执行的程序的状态。 相反,在函数代码中,函数的输出值只依赖于input到函数的参数,所以用参数
x
的相同值调用函数f
两次将产生相同的结果f(x)
。 消除副作用可以使得更容易理解和预测程序的行为,这是函数式编程发展的关键动机之一。
在第2段中说的
相反,在函数代码中,函数的输出值只依赖于input到函数的参数,所以用参数
x
的相同值调用函数f
两次将产生相同的结果f(x)
。
对程序devise来说,这不是一回事吗?
在程序和function方面,应该看到什么呢?
函数式编程
函数式编程是指将函数作为值来处理的能力。
我们来看一个类似于“常规”的值。 我们可以取两个整数值,并使用+
运算符来组合它们以获得一个新的整数。 或者我们可以用一个浮点数乘一个整数来得到一个浮点数。
在函数式编程中,我们可以将两个函数值结合起来,使用合成或提升等运算符产生一个新的函数值。 或者我们可以结合一个函数值和一个数据值来使用map或fold等运算符产生一个新的数据值。
请注意,许多语言都具有函数式编程能力 – 即使是通常不被认为是函数式语言的语言。 即使祖父FORTRAN也支持函数值,尽pipe它没有提供很多function合并操作符。 对于被称为“function性”的语言来说,它需要大规模地支持function性编程能力。
程序编程
程序编程指的是将一个通用的指令序列封装到一个过程中的能力,以便这些指令可以从许多地方被调用而不需要复制和粘贴。 由于程序是编程的早期发展,其能力几乎总是与机器或汇编语言编程所要求的编程风格相联系:强调存储位置和指令在这些位置之间移动数据的概念的风格。
对比
这两种风格并不是真正的对立面 – 它们只是彼此不同而已。 有些语言完全支持这两种风格(例如LISP)。 下面的情况可能会让两种风格有所不同。 让我们写一些代码,用来确定一个列表中的所有单词是否有奇数个字符。 一,程序风格:
function allOdd(words) { var result = true; for (var i = 0; i < length(words); ++i) { var len = length(words[i]); if (!odd(len)) { result = false; break; } } return result; }
我会认为这个例子是可以理解的。 现在,function风格:
function allOdd(words) { return apply(and, map(compose(odd, length), words)); }
从内到外,这个定义做了以下几件事:
-
compose(odd, length)
组合odd
和length
函数来产生一个新函数,用于确定一个string的长度是否是奇数。 -
map(..., words)
为words
每个元素调用新函数,最终返回一个新的布尔值列表,每个布尔值表示相应的单词是否具有奇数个字符。 -
apply(and, ...)
将“和”运算符应用于结果列表, 并将所有的布尔运算放在一起以产生最终结果。
从这些例子中可以看出,程序编程非常关心在variables中移动值,并明确描述产生最终结果所需的操作。 相反,function风格强调将初始input转换为最终输出所需的function组合。
该示例还显示了程序代码与function代码的典型相对大小。 此外,它certificate了程序代码的性能特征比function代码更容易被看到。 考虑:函数是计算列表中所有单词的长度,还是在find第一个偶数长度单词后立即停止? 另一方面,function代码允许高质量的实现来执行一些相当严重的优化,因为它主要expression意图而不是明确的algorithm。
进一步阅读
这个问题出现了很多……例如:
- 过程式编程和函数式编程有什么区别?
- 有人可以给我函数式编程与命令式/程序式编程的例子吗?
- 面向对象与function编程vs程序
John Backus的图灵奖授课详细阐述了函数式编程的动机:
冯诺依曼风格可以解放编程吗?
我不应该在目前的情况下提及这篇文章,因为它非常快速,技术性很强。 我无法抗拒,因为我认为这是真正的基础。
附录 – 2013
评论家指出,stream行的当代语言除了程序和function之外,还提供其他风格的程序devise。 这样的语言通常提供以下一种或多种编程风格:
- 查询(例如,列表parsing,语言集成查询)
- 数据stream(例如隐式迭代,批量操作)
- 面向对象(如封装的数据和方法)
- 面向语言(例如特定于应用程序的语法,macros)
请参阅下面的注释,以了解如何通过这些其他types的可用设施使此应答中的伪代码示例受益。 具体来说,程序性的例子将从几乎任何更高层次的构造的应用中受益。
所展示的例子有意避免混合这些其他的编程风格,以强调所讨论的两种风格之间的区别。
function编程和命令编程的真正区别在于心态 – 命令式编程人员正在思考variables和内存块,而function程序员正在思考“如何将input数据转换为我的输出数据” – 您的“程序”是pipe道并对数据进行变换以将其从input输出到输出。 这是IMO的有趣的部分,而不是“你不应该使用variables”位。
作为这种思维方式的结果,FP计划通常描述将会发生的事情,而不是如何发生的具体机制 – 这是很有力的,因为如果我们能够清楚地说明“select”和“哪里”和“聚合”的含义,我们可以自由地replace它们的实现,就像我们使用AsParallel()一样,突然我们的单线程应用扩展到n个核心。
Isn't that the same exact case for procedural programming?
不,因为程序代码可能有副作用。 例如,它可以在呼叫之间存储状态。
也就是说,可以用程序语言编写满足这个约束条件的代码。 还有可能编写代码来打破这种被认为有效的语言的约束。
我不同意WReach的回答。 让我们分析一下他的答案,看看不同意见的来源。
首先,他的代码是:
function allOdd(words) { var result = true; for (var i = 0; i < length(words); ++i) { var len = length(words[i]); if (!odd(len)) { result = false; break; } } return result; }
和
function allOdd(words) { return apply(and, map(compose(odd, length), words)); }
首先要注意的是他正在混淆:
- 实用
- 面向expression式和
- 迭代器为中心
编程,并且缺less迭代风格编程的能力,以比典型的function风格更具有更明确的控制stream程。
让我们快速谈论这些。
以expression为中心的风格是尽可能地对事物进行评估的一种风格。 尽pipe函数式语言因其对expression式的热爱而着名,但实际上可能有一种没有可组合式expression式的函数式语言。 我要打个电话, 没有expression,只是陈述。
lengths: map words length each_odd: map lengths odd all_odd: reduce each_odd and
这与之前给出的几乎相同,期望函数完全通过声明和绑定链来链接。
以迭代器为中心的编程风格可能是由Python所采取的。 让我们使用一个纯迭代的,以迭代器为中心的风格:
def all_odd(words): lengths = (len(word) for word in words) each_odd = (odd(length) for length in lengths) return all(each_odd)
这不起作用,因为每个子句都是一个迭代过程,它们通过显式暂停和恢复堆栈帧而绑定在一起。 语法可能部分来自function语言,但它被应用于它的完全迭代的实施例。
当然,你可以压缩这个:
def all_odd(words): return all(odd(len(word)) for word in words)
命令现在看起来不那么糟糕,呃? 🙂
最后一点是更明确的控制stream程。 让我们重写原来的代码来使用这个:
function allOdd(words) { for (var i = 0; i < length(words); ++i) { if (!odd(length(words[i]))) { return false; } } return true; }
使用迭代器,你可以有:
function allOdd(words) { for (word : words) { if (!odd(length(word))) { return false; } } return true; }
那么,如果两者之间存在差异,那么函数式语言又有什么意义呢?
return all(odd(len(word)) for word in words)
return apply(and, map(compose(odd, length), words))
for (word : words) { if (!odd(length(word))) { return false; } } return true;
function性编程语言的主要特征是它将突变作为典型编程模型的一部分。 人们通常认为这是一个函数式编程语言没有语句或使用expression式,但这些都是简化。 一个函数式语言用一个行为声明代替了显式计算,然后这个语言执行一个减less。
限制自己的这一部分function可以让您对程序的行为有更多的保证,这样就可以更自由地编写它们。
当你有一个function语言时,创build新的function通常就像编写紧密相关的function一样简单。
all = partial(apply, and)
如果你没有明确地控制一个函数的全局依赖关系,这并不简单,或者甚至是不可能的。 函数式编程的最大特点是可以始终如一地创build更通用的抽象,并相信它们可以组合成一个更大的整体。
在程序范式中(我应该说“结构化编程”吗?),你已经共享了可变内存和指令,它们以一种顺序(一个接一个地)读/写。
在function范式中,你有variables和函数(在math意义上:variables不随时间而变化,函数只能根据它们的input计算出某些东西)。
(这是过于简单的,例如,FPL通常具有用于处理可变内存的设施,而程序语言通常可以支持更高级的程序,所以事情并不清楚;但是这应该给你一个想法)
可爱的Python:来自IBM Developerworks的 Python函数式编程确实帮助我理解了这种差异。
特别是对于一些了解Python的人来说,本文中的代码示例在function和程序上做了不同的事情,可以比较程序化和function化编程之间的区别。
在函数式编程中,为了推理符号的含义(variables或函数名称),您只需要知道两件事情 – 当前范围和符号的名称。 如果你有一个具有不变性的纯函数式语言,那么这两个概念都是“静态的”(对于严重超载的名字),这意味着你可以通过查看源代码来看到当前的范围和名字。
在程序编程中,如果你想回答这个问题,你需要知道你是如何到达那里的,只有范围和名字是不够的。 这就是我所看到的最大的挑战,因为这个执行path是一个“运行时”属性,可以依赖于很多不同的东西,大多数人学习只是debugging它,而不是尝试和恢复执行path。
我最近一直在考虑expression问题的差异。 菲尔·瓦德勒的描述是经常引用的,但是对这个问题的接受答案可能更容易遵循。 基本上,命令式语言似乎倾向于select一种解决问题的方法,而function性语言倾向于select另一种方法。