真正理解程序性和功能性的区别

我真的很难理解 程序上的功能性的编程范例之间的区别。

以下是维基百科 函数式程序设计条目的前两段:

在计算机科学中,功能性的 编程是一种编程范型 将计算视为 数学函数的计算 并避免状态和可变数据 强调... 的应用 功能,相对于 命令式编程风格 强调状态的改变。 函数式编程有其根源 在 lambda 微积分中,一个形式系统 在20世纪30年代为了研究 函数定义,函数 应用程序和递归 函数式编程语言可以 be viewed as elaborations on the Lambda 微积分。

实际上,一个 数学函数和概念 用于祈使语气的“函数” 编程就是这么必要 函数会有副作用, changing the value of program state. Because of this they lack referential 透明度,即同一种语言 表达式可以导致不同的 值在不同的时间取决于 正在执行的程序的状态。 相反,在函数代码中, 函数的输出值取决于 只在作为输入的参数上 函数,因此调用一个函数 对象具有相同值的两次 f argument x will produce the same 两次结果都是 f(x)排除 副作用可以让它变得更容易 来理解和预测这种行为 一个程序,这是其中一个关键 发展的动机 函数式程序设计。

在第二段中写道

相反,在函数代码中,函数的输出值只取决于输入到函数中的参数,因此对参数 x使用相同的值两次调用函数 f将产生相同的结果 f(x)

程序编程不也是这样吗?

一个人应该在程序性和功能性之间寻找什么突出的地方?

58741 次浏览
     Isn't that the same exact case for procedural programming?

不,因为过程代码可能有副作用。例如,它可以存储调用之间的状态。

That said, it is possible to write code that satisfies this constraint in languages considered procedural. And it is also possible to write code that breaks this constraint in some languages considered functional.

来自 IBM Developerworks迷人的 Python: Python 中的函数式编程确实帮助我理解了其中的区别。

Especially for someone who knows Python a bit, the code examples in this article in which doing different things functionally and procedurally are contrasted, can clarify the difference between procedural and functional programming.

函数式和命令式编程式的真正区别在于思维模式——命令式程序员考虑的是变量和内存块,而函数式程序员考虑的是“如何将输入数据转换为输出数据”——你的“程序”是 资料上的管道和转换集,将它从输入转换为输出。这是有趣的部分 IMO,而不是“你不应该使用变量”位。

作为这种心态的结果,FP 程序通常描述 什么将会发生,而不是 怎么做的特定机制将会发生——这是强大的,因为如果我们能够清楚地说明什么是“选择”、“在哪里”和“聚合”意味着,我们可以自由地交换它们的实现,就像我们对 AsParle()所做的那样,突然我们的单线程应用扩展到 N核心。

我最近一直在思考 表情问题的不同之处。Phil Wadler's description经常被引用,但是 这个问题的公认答案可能更容易理解。基本上,命令式语言倾向于选择一种解决问题的方法,而函数式语言倾向于选择另一种。

在程序范式中(我应该用“结构化编程”来代替吗?)您共享了可变内存和指令,这些指令按照某种顺序(一个接一个)读/写它。

在函数范式中,有变量和函数(在数学意义上: 变量不随时间变化,函数只能根据它们的输入来计算)。

(This is oversimplified, e.g., FPLs typically have facilities for working with mutable memory whereas procedural languages can often support higher-order procedures so things are not as clear-cut; but this should give you an idea)

函数式编程

函数式编程指的是将函数视为值的能力。

让我们考虑一个与“正则”值的类比。我们可以获取两个整数值,然后使用 +运算符将它们组合在一起,得到一个新的整数。或者我们可以用一个整数乘以一个浮点数来得到一个浮点数。

在函数式编程中,我们可以组合两个函数值,使用像 作曲抬起来这样的运算符生成一个新的函数值。或者我们可以组合一个函数值和一个数据值,使用像 地图弃牌这样的运算符生成一个新的数据值。

请注意,许多语言都具有函数式编程功能——甚至是通常不被认为是函数式语言的语言。即使是 FORTRAN 祖父也支持函数值,尽管它没有提供多少函数组合运算符。对于一种被称为“函数式”的语言,它需要在很大程度上包含函数式编程功能。

程序编程

程序编程是指能够将一个常见的指令序列封装成一个过程,以便这些指令可以从许多地方调用,而无需复制和粘贴。由于过程在编程中是一个非常早期的发展,这种能力几乎总是与机器或汇编语言编程所要求的编程风格联系在一起: 这种风格强调存储位置的概念和在这些位置之间移动数据的指令。

对比

这两种风格并非完全相反——它们只是彼此不同而已。有些语言完全包含这两种样式(例如 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 allOdd(words) {
return apply(and, map(compose(odd, length), words));
}

从里到外,这个定义做了以下事情:

  1. compose(odd, length)结合了 oddlength函数来生成一个新函数,该函数确定字符串的长度是否为奇数。
  2. map(..., words)words中的每个元素调用这个新函数,最终返回一个新的布尔值列表,每个布尔值表示对应的单词是否具有奇数个字符。
  3. apply(and, ...)将“ and”运算符应用于生成的列表,还有将所有布尔值放在一起生成最终结果。

从这些例子中可以看出,程序编程非常关注在变量中移动值,并明确描述生成最终结果所需的操作。相比之下,函数样式强调将初始输入转换为最终输出所需的函数的组合。

该示例还显示了过程代码与函数代码的典型相对大小。此外,它还表明过程代码的性能特征可能比函数代码的性能特征更容易看到。考虑一下: 函数是计算列表中所有单词的长度,还是每个单词在找到第一个偶数长度的单词后立即停止?另一方面,函数代码允许高质量的实现执行一些非常严肃的优化,因为它主要表达的是意图,而不是显式的算法。

进一步阅读

这个问题经常出现,比如说:

John Backus 的图灵奖演讲详细阐述了函数式编程的动机:

编程能从冯诺依曼风格中解放出来吗?

我真的不应该在现在的情况下提到这篇论文,因为它变得非常专业,非常快。我就是忍不住,因为我觉得这才是真正的基础。


增编-2013年

Commentators point out that popular contemporary languages offer other styles of programming over and above procedural and functional. Such languages often offer one or more of the following programming styles:

  • 查询(例如列表理解、语言集成查询)
  • 数据流(例如隐式迭代、批量操作)
  • object-oriented (e.g. encapsulated data and methods)
  • 面向语言(例如,特定于应用程序的语法、宏)

请参阅下面的注释,以获得这个响应中的伪代码示例如何从其他样式的一些可用工具中受益的示例。特别是,过程示例将受益于实际上任何更高级别构造的应用程序。

所展示的示例有意避免混合其他编程风格,以强调讨论中的两种风格之间的区别。

在函数式编程中,为了推断符号(变量或函数名)的含义,你只需要知道两件事——当前作用域和符号的名称。如果您有一个具有不可变性的纯函数式语言,那么这两个概念都是“静态的”(对于严重重载的名称感到抱歉) ,这意味着您可以通过查看源代码同时看到当前范围和名称。

在程序编程中,如果你想回答这个问题,你还需要知道你是如何做到的,仅仅知道范围和名称是不够的。这是我认为最大的挑战,因为这个执行路径是一个“运行时”属性,可以依赖于很多不同的东西,大多数人学会只调试它,而不是尝试恢复执行路径。

我不同意韦奇的回答。让我们稍微解构一下他的回答,看看不同意的地方在哪里。

首先,他的原则是:

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 oriented and
  • Iterator centric

并且缺乏迭代样式编程的能力,无法比典型的函数样式具有更明确的控制流。

让我们快速讨论一下这些。

以表达式为中心的风格是一种把事物尽可能多地从 评估转化为事物的风格。虽然函数式语言以其对表达式的喜爱而闻名,但是实际上不使用可组合表达式也可以使用函数式语言。我要编一个,里面有 没有表达式,仅仅是语句。

lengths: map words length
each_odd: map lengths odd
all_odd: reduce each_odd and

This is pretty much the same as given before, except functions are chained purely through chains of statements and bindings.

以迭代器为中心的编程风格可能是 Python 采用的风格:

def all_odd(words):
lengths = (len(word) for word in words)
each_odd = (odd(length) for length in lengths)
return all(each_odd)

This is not functional, because each clause is an iterative process, and they are bound together by explicit pause and resumption of the stack frames. The syntax may be inspired partially from a functional language, but it is applied to a completely iterative embodiment of it.

当然,你可以压缩这个:

def all_odd(words):
return all(odd(len(word)) for word in words)

命令现在看起来没那么糟了,是吧? :)

最后一点是关于更加明确的控制流:

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;


函数式编程语言的主要明确特征是它消除了作为典型编程模型一部分的变异。人们通常认为这意味着函数式编程语言没有语句或使用表达式,但这些都是简化。函数式语言用行为声明取代显式计算,然后对行为声明执行约简。

将自己限制在这个功能子集中允许您对程序的行为有更多的保证,这允许您更自由地组合它们。

使用函数式语言时,创建新函数通常与组合紧密相关的函数一样简单。

all = partial(apply, and)

如果没有显式地控制函数的全局依赖关系,这就不简单,甚至可能不可能。函数式编程最好的特性是,您可以始终如一地创建更通用的抽象,并相信它们可以组合成一个更大的整体。

两种编程范例之间的一个明显区别是状态。

在函数式编程中,避免使用状态。简单地说,就是不会有被赋值的变量。

例如:

def double(x):
return x * 2


def doubleLst(lst):
return list(map(double, action))

然而,程序编程使用状态。

例如:

def doubleLst(lst):
for i in range(len(lst)):
lst[i] = lst[i] * 2  # assigning of value i.e. mutation of state
return lst