函数式、声明式和命令式编程

480

什么是函数式、声明式和命令式编程?


4
这里有一些很棒的答案。一个有趣的但尚未完全阐明的事情是,声明式命令式是互补和共生的,不仅仅是不同风格或做什么如何做的区别。 - Kit
1
@Kit Imo,这个页面上的一些答案混淆了术语。DP == 引用透明度(RT)。DP和IP是相反的,因此在我看来它们不是整体的补充,即整个程序可以用任一风格编写。对函数的调用可以是DP(RT)或IP,其实现可以是任一种或混合。它们不是共生的意思是,在DP函数中调用IP函数不能使DP函数的调用变为IP。它们是共生的意思是,现实世界(例如功能反应)的程序可以使用混合,例如IP顶层调用DP函数。 - Shelby Moore III
1
应该将其添加到维基或类似维基的链接上等。这里有一个很棒的维基百科链接:http://en.wikipedia.org/wiki/Comparison_of_programming_paradigms - Joe
jQuery点赞!http://meta.stackexchange.com/a/19492 - return true
1
这个问题正在Meta上讨论:http://meta.stackoverflow.com/q/342784/2751851 - duplode
14个回答

272
在撰写本文时,此页面上得票最高的答案在陈述式和命令式定义方面不够精确和混乱,包括引用维基百科的答案。一些答案以不同的方式混淆了这些术语。
还请参考我对电子表格编程为何是陈述式的解释
此外,有几个答案声称函数式编程必须是陈述式的子集。在这一点上,它取决于我们是否区分“函数”和“过程”。让我们先处理陈述式与命令式。
陈述式表达式的定义
唯一可能区分陈述式表达式和命令式表达式的属性是其子表达式的引用透明度(RT)。所有其他属性要么在两种类型的表达式之间共享,要么是从RT派生的。
一个100%的陈述式语言(即每个可能的表达式都是RT)不允许更改存储值,例如HTML和大多数Haskell。 RT表达式的定义 RT通常被称为“无副作用”。术语“效果”没有明确的定义,因此有些人不同意“无副作用”等同于RT。RT有一个精确定义

如果对于所有程序p,表达式ep中的每个出现都可以替换为评估e的结果,而不影响p的可观察结果,则表达式e是引用透明的。

由于每个子表达式在概念上都是一个函数调用,所以RT要求函数的实现(即调用函数内部的表达式)不得访问外部的可变状态(允许访问可变的本地状态)。简而言之,函数(实现)应该是纯的。 纯函数的定义 纯函数通常被认为是“无副作用”的。术语“效果”没有精确定义,因此有些人不同意这个说法。
纯函数具有以下特征:
- 唯一可观察的输出是返回值。 - 唯一的输出依赖是参数。 - 在生成任何输出之前,参数已完全确定。
请记住,RT适用于表达式(包括函数调用),而纯度适用于(实现)函数。
一个不太常见的例子是破坏RT表达式的不纯函数并发性,但这是因为纯度在中断抽象层被打破。你真的不需要知道这些。要创建RT表达式,可以调用纯函数。
RT的其他属性
任何其他声明式编程所引用的属性,例如维基百科使用的1999年引用,要么源自RT,要么与命令式编程共享。因此证明了我的精确定义是正确的。
注意,外部值的不可变性是实时要求的子集。
声明式语言没有循环控制结构,例如“for”和“while”,因为由于不可变性,循环条件永远不会改变。
声明式语言不表达除嵌套函数顺序(即逻辑依赖关系)以外的控制流,因为由于不可变性,其他评估顺序的选择不会改变结果。
声明式语言表达逻辑“步骤”(即嵌套RT函数调用顺序),但每个函数调用是否为更高级别的语义(即“要做什么”)不是声明式编程的要求。与命令式的区别在于,由于不可变性(更一般地说是RT),这些“步骤”不能依赖于可变状态,而只能依赖于表示逻辑的关系顺序(即函数调用的嵌套顺序,也称为子表达式)。
例如,HTML段落<p>在段落中的子表达式(即标签)被评估之前无法显示。没有可变状态,只有由标记层次结构的逻辑关系(子表达式嵌套,类比嵌套函数调用)引起的顺序依赖性。
因此,存在不可变性(更普遍地是RT的导数属性),即声明性表达式仅表达组成部分(即子表达式函数参数)的逻辑关系,而不表达可变状态关系。
评估顺序
子表达式的评估顺序的选择只在任何函数调用不是RT(即函数不是纯函数)时才会产生不同的结果,例如,在函数内访问某些外部可变状态。
例如,给定一些嵌套表达式,例如f(g(a,b),h(c,d)),如果函数fgh是纯函数,则函数参数的急切和惰性求值将产生相同的结果。
然而,如果函数fgh不是纯函数,则评估顺序的选择可能会导致不同的结果。
注意,嵌套表达式在概念上是嵌套函数,因为表达式运算符只是伪装成一元前缀、一元后缀或二元中缀符号的函数调用。
顺便说一下,Haskell有一个不同的语法f(g a b)(h c d)
评估顺序细节:
一个函数是从输入到输出的状态转换(而不是可变的存储值)。对于纯函数的RT调用组合,这些状态转换的执行顺序是独立的。每个函数调用的状态转换是独立的,由于缺乏副作用和原则,即RT函数可以被其缓存值替换。为了纠正一种流行的误解, 纯单子组合总是声明性和RT,尽管Haskell的IO单子可能是不纯的,因此在程序外部的World状态方面具有命令式的特点(但在下面的警告意义上,副作用是隔离的)。
热衷评估意味着函数参数在函数调用之前被评估,惰性评估意味着直到在函数内部访问它们时(如果有必要),参数才会被评估定义:函数参数在函数定义处声明,而函数参数在函数调用处提供。了解参数参数之间的区别。
从概念上讲,所有表达式都是(由)函数调用组成的,例如常量是没有输入的函数,一元运算符是具有一个输入的函数,二元中缀运算符是具有两个输入的函数,构造函数是函数,甚至控制语句(例如ifforwhile)也可以用函数建模。这些“参数”函数的求值顺序(不要与嵌套函数调用顺序混淆)在语法中未声明,例如f( g() )可以急切地评估g然后对g的结果进行f的评估,或者只有在f内部需要g的结果时才会惰性地评估g
警告:没有允许无限递归的Turing complete语言是完全声明性的,例如惰性求值会引入内存和时间上的不确定性。但这些由于求值顺序选择而导致的副作用仅限于内存消耗、执行时间、延迟、非终止和外部滞后,因此需要外部同步。 函数式编程 由于声明性编程不能使用循环,所以唯一的迭代方式是函数递归。在这个意义上,函数式编程与声明性编程有关。

但是函数式编程并不仅限于声明式编程。函数式组合可以与子类型进行对比,特别是在表达式问题方面,其中扩展可以通过添加子类型或者函数分解来实现。扩展可以是两种方法的混合

函数式编程通常将函数作为一等对象,这意味着函数类型可以出现在语法中的任何其他类型可以出现的地方。其结果是函数可以输入和操作函数,从而通过强调函数组合来实现关注点分离,即将确定性计算的子计算之间的依赖关系分离。

例如,函数式编程不是为每个集合元素应用可能的无限个特定操作而编写单独的函数(如果函数还必须是声明式的,则使用递归而不是循环),而是使用可重用的迭代函数,例如map、fold、filter。这些迭代函数输入第一类专业动作函数。这些迭代函数遍历集合并为每个元素调用输入的专业动作函数。由于这些动作函数不再需要包含循环语句来迭代集合,因此它们更加简洁。
然而,请注意,如果函数不是纯函数,那么它实际上就是一个过程。我们可以认为使用不纯函数的函数式编程实际上就是过程式编程。因此,如果我们同意声明表达式是RT,那么我们可以说过程式编程不是声明式编程,因此我们可以认为函数式编程总是RT,并且必须是声明式编程的一个子集。
并行性

这个具有一等函数的功能组合可以通过分离独立函数来表达并行深度

Brent原则:具有工作量w和深度d的计算可以在p处理器PRAM中以时间O(max(w/p,d))实现。

并发和并行也需要声明式编程,即不可变性和RT。

那么并行性=并发性的这种危险假设从哪里来?这是具有副作用的语言的自然结果:当你的语言到处都有副作用时,每次尝试同时执行多个操作时,你基本上都会出现由每个操作的效果交错引起的非确定性。因此,在具有副作用的语言中,获得并行性的唯一方法是并发性;因此,我们经常看到这两个概念被混淆是不足为奇的。

FP评估顺序

请注意,函数组合的终止和性能副作用也受到评估顺序的影响。
急切(CBV)和懒惰(CBN)是范畴对偶[10],因为它们具有相反的评估顺序,即分别评估外部或内部函数。想象一个倒置的树,那么急切从函数树的分支顶部向上评估分支层次结构到顶级函数主干;而懒惰则从主干向下评估到分支顶部。急切没有合取积(“and”,也称为范畴“积”),而懒惰没有析取余积(“or”,也称为范畴“和”)[11]。
性能
- 急切
与非终止一样,对于合取函数组合来说,eager(渴望求值)也太过于急切,即组合控制结构会做一些不必要的工作,而这些工作在lazy(惰性求值)中并没有进行。例如,当eager与一个在第一个true元素上终止的fold(折叠)组合时,eager会急切地将整个列表映射为布尔值,这是不必要的。

这种不必要的工作是导致eager和lazy在纯函数时间复杂度方面存在“高达”额外log n因子差异的原因。解决方法是使用functor(例如列表)和具有可选惰性乘积的lazy constructors(即eager),因为对于eager而言,错误的渴望起源于内部函数。这是因为乘积是构造类型,即在初始不动点上具有初始代数的归纳类型[11]

  • Lazy
与非终止一样,惰性在析取函数组合中也过于懒惰,即共归结的最终性可能比必要的晚发生,导致不必要的工作和延迟的不确定性,这在急切模式下是不存在的[10][11]。最终性的例子包括状态、时间、非终止和运行时异常。这些都是命令式副作用,但即使在纯声明性语言(例如Haskell)中,也存在于命令式IO单子中的状态(注意:并非所有单子都是命令式!),而时间则是相对于命令式实际世界的状态。即使使用惰性与可选的急切余积,也会将“惰性”泄漏到内部余积中,因为使用惰性时,惰性不正确性来自外部函数(请参见非终止部分的示例,其中==是一个外部二元运算符函数)。这是因为余积受限于终局性,即具有终极对象上的终极代数的共归纳类型[11]。
懒惰计算会导致函数设计和调试中的延迟和空间不确定性,调试可能超出大多数程序员的能力范围,因为在声明的函数层次结构和运行时评估顺序之间存在不协调。使用急切求值的懒惰纯函数可能会在运行时引入以前未曾见过的非终止情况。相反,使用懒惰求值的急切纯函数可能会在运行时引入以前未曾见过的空间和延迟不确定性。
非终止:
在编译时,由于停机问题和图灵完备语言中的相互递归,通常不能保证函数终止。
- 急切求值:
对于 Head 和 Tail 的合取式,如果 Head 或 Tail 中的任何一个不终止,则 List( Head(), Tail() ).tail == Tail() 或 List( Head(), Tail() ).head == Head() 中的左侧不成立,而右侧成立。

然而,使用惰性时双方都终止。因此,在连词产品中,热切过于热切,并且在不必要的情况下不会终止(包括运行时异常)。

  • 惰性

对于1“或”2的析取,如果f不终止,则List(f?1:2,3).tail ==(f?List(1,3):List(2,3)).tail不成立,因为左侧终止,右侧不终止。

然而,使用渴望时,两边都不终止,因此永远不会达到相等测试。因此,惰性对于分离余积来说太懒了,在这些情况下,它无法终止(包括运行时异常),而比渴望做更多的工作。

[10] Filinski的声明性延续和范畴对偶,第2.5.4节CBV和CBN的比较,以及第3.6.1节SCL中的CBV和CBN。

【2023-03-17】

[11] Filinski的《声明性续延和范畴对偶》,第2.2.1节产品和余积,第2.2.2节终点和起点对象,第2.5.2节采用惰性乘积的CBV,以及第2.5.3节采用急切余积的CBN。


3
缩写并不意味着提供一个定义。当我写道“RT通常缩写为'无副作用'”时,这并不意味着RT的定义是“无副作用”,因为人们可能对“作用”有不同的定义。如果我改而说“RT通常被缩写为'xyz'”,一个没有意义的符号并不能给RT提供任何定义。RT有一个精确的定义,无论使用什么符号来引用它,都不会改变其定义。 - Shelby Moore III
1
C in ESP style与State monad中的RT等同起来是无效的,因为每个C语句可能会改变全局状态,而在state monad中,每个相应的语句都会生成一个状态的副本(已修改)。后者是RT--前者不是。单子组合始终是RT。DP == RT是DP的唯一属性集(数学证明我是正确的,否则DP就没有意义)。 - Shelby Moore III
1
有些人说SQL是一种声明性语言。如果是这样,那么SQL是否没有RT,因为它可以修改状态(数据库中的表)?在程序的开头和结尾调用相同的SQL查询将产生不同的结果。 - gsgx
1
我希望我能够理解这个句子之后的内容。我正在阅读 DAX 的手册,它表明它是一种“函数式语言”。这是什么意思?我不知道,去问你爸吧。 - Nick.McDermaid
1
你引用的“引用透明度”的定义没有来源,也不符合文献定义;请参考Uday Reddy的回答获取正确的定义。 - Géry Ogam
显示剩余8条评论

105

对于这些词汇,实际上没有非歧义、客观的定义。以下是我个人的定义:

命令式 - 着重于计算机应该执行哪些步骤,而不是计算机将做什么(例如C、C++、Java)。

声明式 - 着重于计算机应该做什么,而不是如何做到(例如SQL)。

函数式 - 是声明性语言的一个子集,强调递归。


1
请记住以下几点:1)解释旨在简单易懂,而非全面详尽;2)正如我所说,定义这些语言的方式有多种。因此,对于你来说答案可能是错误的,但对其他人来说则是正确的。 - Jason Baker
3
函数式编程并不是“声明式语言的子集”。声明式编程要求存储值的不可变性,而函数式编程不需要,除非它是纯FP。请参见我的回答。也可以查看电子表格单元格的解释。正确的“客观”定义并不“模糊”。命令式编程也专注于“计算机应该做什么”。唯一的区别是命令式编程必须处理可变的存储值。 - Shelby Moore III
5
@ShelbyMooreIII - 我倾向于同意Eric Meijer的观点。实际上并不存在所谓的“非纯函数式语言”。就我而言,Ocaml、F#等都是带有函数式数据结构的命令式语言。但正如我在回答中所说,我不认为这个问题有任何客观、明确的答案。事物的定义有多种方式。 - Jason Baker
3
如果所选属性不是一个不相交的集合,当定义都不明确时,数学上可以证明某人混淆了术语。如果您将FP定义为仅_FP_(即RT),则与DP无异,参见[我的回答](https://dev59.com/DHRB5IYBdhLWcg3wgXdV#8357604)。 FP的不相交属性包括第一类函数类型,它可以是命令式函数。我在这里找到了更基本的术语歧义[这里](https://dev59.com/YnVD5IYBdhLWcg3wU56H)和[这里](http://stackoverflow.com/a/8450076/615784)。偏爱_FP纯粹_FP与_FP的定义无关。 - Shelby Moore III
21
@ShelbyMooreIII - 我的假设是OP希望得到英文回答,而不是数学术语。如果这个假设是错误的,那么我向你道歉。 - Jason Baker
显示剩余6条评论

61

指令式声明式是两种相反的编程风格。指令式是传统的“逐步配方”方法,而声明式更多地是“这就是我想要的结果,你自己想出如何做到它”。

这两种方法在整个编程中都会出现-即使是使用相同语言和相同程序也会如此。一般来说,声明式方法被认为更可取,因为它使程序员不必指定太多细节,同时具有更少的错误机会(如果您描述所需的结果,并且某些经过充分测试的自动处理程序可以从该结果向后工作以定义步骤,则可能希望事情更可靠,而不必手动指定每个步骤)。

另一方面,指令式方法给您更多的低级控制-它是编程的“微管理者方法”。这样可以让程序员利用关于问题的知识提供更有效的答案。因此,在某些部分以较声明式的风格编写程序是很常见的,但是对于速度关键部分则更倾向于采用较指令式的方法。

正如您所想象的那样,您用于编写程序的语言会影响您可以声明性的程度-具有内置的“智能”以在给定结果描述的情况下工作的语言将允许比需要程序员首先使用指令式代码添加这种类型智能才能构建更声明性层的语言更声明性的方法。例如,像Prolog这样的语言被认为非常声明性,因为它内置了一个搜索答案的过程。

到目前为止,您会注意到我没有提到函数式编程。那是因为它是一个意义与其他两个不直接相关的术语。在其最简单的形式下,函数式编程意味着您使用函数。特别地,您使用支持函数作为“一等值”的语言-这意味着您不仅可以编写函数,还可以编写编写函数的函数(编写函数的函数....),并将函数传递给函数。简而言之,函数像字符串和数字等东西一样灵活而常见。

然而,与此相反的是,函数式编程、命令式编程和声明式编程经常被一起提到。这是因为将函数式编程的思想“极致化”所导致的结果。在其最纯粹的意义上,函数是来自数学的——一种类似于“黑匣子”的东西,它接收一些输入并始终给出相同的输出。这种行为不需要存储可变变量。因此,如果你设计一种编程语言,旨在实现非常纯净、受数学影响的函数式编程思想,你最终会在很大程度上拒绝可变值的概念(以某种有限的技术意义上)。

如果你这样做——限制变量如何改变——那么差不多就会无意中迫使程序员编写更多的声明式代码,因为命令式编程的一个很大部分是描述变量如何改变,而你不能再这样做了!因此,事实证明,函数式编程——特别是在函数式语言中编程——往往会产生更多的声明式代码。

总之:

  • 命令式编程和声明式编程是两种相反的编程风格(鼓励这些风格的编程语言使用相同的名称)。

  • 函数式编程是一种编程风格,其中函数变得非常重要,结果可变值的重要性减弱了。有限的指定值更改的能力迫使采用更多声明式风格。

因此,“函数式编程”经常被描述为“声明式”。


6
迄今为止最好的解释。它似乎表明,函数式编程和面向对象编程与命令式编程和声明式编程是正交的。 - Didier A.
你认为逻辑编程是声明式的吗?还是它本身是正交的? - Didier A.

51

简而言之:

命令式语言 指定计算机按顺序执行的一系列指令(做这个,然后做那个)。

声明式语言 声明了关于哪些输入应该产生哪些输出的一组规则(例如,如果你有A,则结果是B)。引擎将这些规则应用于输入,并给出输出。

函数式语言 声明了一组数学/逻辑函数,定义了如何将输入转换为输出。例如,f(y) = y * y。它是声明式语言的一种类型。


1
函数式编程不是“声明式语言的一种类型”。声明式编程要求存储值的不可变性,而_impure_函数式编程则不需要。请参见我的答案。还可以参考电子表格单元格的解释。命令式逻辑(又称指令)之所以按顺序执行,仅仅是由于存在可变存储值,结果取决于评估顺序。使用您的词汇表,一个“指令”可以(而一个“规则”不能)对可变值进行操作。 - Shelby Moore III

25

指令式编程: 如何 实现我们的目标

   Take the next customer from a list.
   If the customer lives in Spain, show their details.
   If there are more customers in the list, go to the beginning

声明式:我们想要实现的是什么

   Show customer details of every customer living in Spain

你正在描述函数式编程与非FP编程,而不是声明式与命令式编程之间的极性。函数式编程与命令式和声明式编程之间的极性是正交的。声明式编程要求存储值的不可变性,而“不纯”的函数式编程则不需要。请参见我的答案 - Shelby Moore III

23

命令式编程指的是任何一种程序设计风格,其中你的程序由描述计算机操作方式的指令构成。

声明式编程指的是任何一种程序设计风格,其中你的程序是问题或解决方案的描述,但不明确说明如何完成工作。

函数式编程通过求值函数和函数的函数进行编程... 严格定义的函数式编程意味着通过定义无副作用的数学函数进行编程,因此它是声明式编程的一种形式,但并不是唯一的声明式编程形式。

逻辑编程(例如 Prolog)是另一种声明式编程形式。它通过决定逻辑语句是否为真(或是否可以满足)来计算。程序通常是一系列事实和规则 - 即一种描述而不是指令序列。

术语重写(例如 CASL)是另一种声明式编程形式。它涉及对代数术语进行符号转换。它与逻辑编程和函数式编程完全不同。


函数式编程不是“声明式编程”的一种形式。声明式编程要求存储的值具有不可变性,但是非纯的函数式编程则没有这个要求。请参见我的答案。也请参见电子表格单元格的说明。在“描述如何完成工作”的术语中,“工作”一词没有被定义。唯一的原因是命令式逻辑(也称为“指令”)按顺序执行是由于存在可变的存储值,结果取决于评估顺序。 - Shelby Moore III
3
请理解我所说的是纯函数式编程。这些范式可以相互重叠,我不想被淹没在比较混合语言上。至少在理论上,函数式编程是关于函数而非描述计算机如何执行每个计算的 - 因此我认为它是声明式的。 - Dafydd Rees
我编辑了我的回答,并在“函数式编程”部分下添加了一个场景,其中我们可以争论FP始终是纯的,而不纯的FP实际上是“过程式编程”。抱歉之前没有包含这个解释。 - Shelby Moore III

16

命令式表达式描述需要执行的一系列操作(关联)

声明式表达式是对程序行为做出贡献的声明(关联、交换律、幂等、单调性)

函数式表达式只有一个值作为效果;其语义支持等式推导


1
声明式表达式有助于程序的预期行为,而命令式则可能对预期或非预期行为产生影响。如果这是有意义的语义,声明式不需要是可交换和幂等的。我喜欢你简洁的函数本质,所以我点了赞。 - Shelby Moore III

14
自从我写了之前的答案以来,我已经制定了一个关于声明性属性的new definition,如下所述。我还将命令式编程定义为对偶属性。
这个定义比我在之前的答案中提供的更好,因为它简洁而且更通用。但是,它可能更难理解,因为适用于编程和人生的不完备定理的含义对人类来说很难理解。
定义的引用解释讨论了纯函数式编程在声明性编程中的作用。
所有奇特类型的编程都符合以下声明性与命令式的分类法,因为以下定义声称它们是对偶的。
声明式 vs 命令式
声明式属性是奇怪的、晦涩的,很难用一个技术上精确且通用的定义来捕捉它,因为这是一个天真的概念,即我们可以在不产生意外副作用的情况下声明程序的含义(也就是语义)。表达含义和避免意外效应之间存在内在的张力,这种张力实际上源于编程和我们的宇宙的不完备定理
将声明性定义为“要做什么”,将命令式定义为“如何去做”是一种过度简化、技术上不精确、常常存在歧义的定义。一个模糊的例子是,当一个程序输出另一个程序时——编译器——“要做什么”就变成了“如何做”。
显然,使语言图灵完全的无限递归,在语义上也类似——不仅仅是在评估的句法结构(即操作语义)中。这在逻辑上是哥德尔定理的一个类比例子——“任何完备的公理系统也是不一致的”。思考一下这个矛盾的怪异性!这也是一个例子,说明了表达语义并没有可证明的界限,因此我们无法证明一个程序(以及类比的语义)停机,即停机定理。
不完备定理源于我们宇宙的基本特性,正如热力学第二定律所述,“(也就是独立可能性的数量)永远趋向于最大值”。程序的编码和设计永远不会完成——它是活生生的!——因为它试图满足现实世界的需求,而现实世界的语义总是在变化和趋向更多的可能性。人类永远不会停止发现新事物(包括程序中的错误;-)。
要在这个没有边缘的怪异宇宙中准确而技术地捕捉这个期望的概念,需要一个简洁但具有欺骗性的定义,直到深入解释才会听起来正确。
定义:
声明性属性是指每种特定模块化语义都只能存在一组可能的语句集合。
命令式属性是它的对偶,其中语义在组合下不一致,或者可以用语句集合的变化来表达。
这个声明性定义在语义范围上是明显的局部的,意味着它要求模块化的语义在全局范围内无论何时何地实例化和使用都保持其一致的含义。因此,每个声明性模块化语义应该本质上与所有可能的其他语义正交——而不是一个由于不完备定理而不可能(全局算法或模型)的见证一致性,这也是 Robert Harper 的“《更多不总是更好》”中所阐述的观点,他是卡内基梅隆大学计算机科学教授和标准 ML 的设计者之一。
这些模块化声明性语义的示例包括范畴论函子,例如 Applicative,名义类型、命名空间、命名字段,以及就操作语义的操作级别而言则是纯函数式编程。
因此,设计良好的声明性语言可以更清

编辑:我在Robert Harper的博客上发布了以下评论

在函数式编程中,变量的取值范围是一个类型。根据人们如何区分函数式和命令式编程,命令式程序中的“可赋值项”也可能有一个类型,这限制了它的可变性。我目前唯一明确理解函数式编程的定义是:a) 将函数作为第一类对象和类型;b) 喜欢使用递归而非循环;c) 纯函数——即这些函数不会在记忆化时影响程序所需的语义(因此完全纯的函数式编程不存在于通用指称语义中,由于操作语义,如内存分配的影响)。纯函数的幂等性质意味着可以将其变量上的函数调用替换为其值,但对于命令式过程的参数来说通常情况下并非如此。相对于输入和结果类型之间未组合状态转移,纯函数似乎是声明式的。但是纯函数的组合没有保持任何此类一致性,因为可以在纯函数式编程语言中模拟具有副作用(全局状态)的命令式过程,例如Haskell的IOMonad,而且在任何图灵完备的纯函数式编程语言中都无法防止进行这样做。因此,我得出结论,只有非图灵完备的语言才能是声明式的。因此,一个明确而独特的声明式语言属性可以是其输出可以被证明服从某个可枚举的生成规则集合。例如,对于任何未编写脚本(即非图灵完备)的特定HTML程序(忽略解释器发散的方式的不同),其输出变化范围是可枚举的。更简洁地说,一个HTML程序是其变量可变性的纯函数。电子表格程序也是其输入变量的纯函数。因此,对我来说,声明式语言是 无限递归的对立面,即按照哥德尔的第二不完备定理,自指定理不能被证明。Lesie Lamport 写了一篇童话故事,讲述了欧几里得如何绕过哥德尔不完备性定理,应用于编程语言上的数学证明,通过类型和逻辑之间的相似之处(柯里-霍华德对应等)。

Robert Harper似乎同意我关于大多数声明性定义无意义的观点,但我不认为他看过我上面的那个。他在讨论指示语义时接近了我的定义,但并没有达到我的定义。模型(指示语义)是更高级的。 - Shelby Moore III
你如何分类SQL? - Géry Ogam

9

命令式编程:告诉“机器”如何做某事,结果你想要发生的事情就会发生。

声明式编程:告诉“机器”你想要发生什么,让计算机自己去解决如何实现。

命令式编程示例

function makeWidget(options) {
    const element = document.createElement('div');
    element.style.backgroundColor = options.bgColor;
    element.style.width = options.width;
    element.style.height = options.height;
    element.textContent = options.txt;

    return element;
}

声明式编程示例

function makeWidget(type, txt) {
    return new Element(type, txt);
}

注意:区别不在于简洁性、复杂性或抽象性。正如所述,区别在于“如何”与“什么”。

2
好的建议,但如果您能提供至少一个示例,那就更好了! - Pardeep Jain

4

现今,新的焦点:我们需要旧的分类法吗?

过去使用 命令式/声明式/函数式 方面对通用语言进行分类是不错的,但是现在所有“大型语言”(如Java、Python、Javascript等)都有一些选项(通常是框架),可以用“其他焦点”(通常是命令式)来表达,并且可以表达并行进程、声明性函数、lambda等。

因此,一个好的问题变体是“现在用什么方面来分类框架比较好?”......重要的方面是我们可以标记为“编程风格”......

关注数据与算法的融合

一个很好的例子来解释。正如你可以在维基百科上阅读有关jQuery的内容,

由其选择器引擎启用的一组jQuery核心功能——DOM元素选择、遍历和操作——创造了一种新的“编程风格”,将算法和DOM数据结构融合在一起

因此,jQuery是一个关注于“新编程风格”的最佳(流行)例子,它不仅是面向对象的,而且是“融合了算法和数据结构”。jQuery有点像电子表格一样反应灵敏,但不是“单元格导向”,而是“基于DOM节点的”... 在这个背景下比较主要的“风格”:

  1. 无融合:在所有“大语言”中,任何函数式/声明式/命令式表达式都不会融合数据和算法,除非是一些面向对象编程,这从严格的代数结构角度来看是一种融合。

  2. 部分融合:现今所有经典的融合策略都有一些框架将其作为范例... 数据流编程事件驱动编程(或旧领域特定语言如awkXSLT)...像使用现代电子表格进行编程一样,它们也是响应式编程风格的示例。

  3. 大规模融合:就是“jQuery风格”... jQuery是一个专注于“融合算法和DOM数据结构”的领域特定语言。
    PS:其他“查询语言”,如XQuery、SQL(带有PL作为命令式表达式选项)也是数据-算法-融合的例子,但它们是孤立的,没有与其他系统模块的融合...当使用find()-变量和规范子句时,Spring是另一个很好的融合示例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接