从概念上讲,所有表达式都是(一个组合的)函数调用,例如常量是没有输入的函数,一元运算符是具有一个输入的函数,二进制中缀运算符是具有两个输入的函数,构造函数是函数,甚至控制语句(例如 if , for , while )可以用函数建模 . 评估的order that these参数函数(不要与嵌套函数调用顺序混淆)不是由语法声明的,例如 f( g() ) 可以急切地评估 g 然后 f 对 g 的结果,或者它可以评估 f 并且仅在 f 内需要其结果时懒惰地评估 g .
imperative language 指定计算机按顺序执行的一系列指令(执行此操作,然后执行此操作) .
declarative language 声明了一组关于哪些输出应该来自哪些输入的规则(例如,如果你有A,那么结果是B) . 引擎会将这些规则应用于输入,并提供输出 .
functional language 声明了一组数学/逻辑函数,用于定义输入如何转换为输出 . 例如 . f(y)= y * y . 它是一种声明性语言 .
2
势在必行: how 实现我们的目标
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
声明: what 我们要实现
Show customer details of every customer living in Spain
97
Imperative Programming 表示程序结构的任何编程风格 describing how the operations performed by a computer will happen .
Declarative Programming 表示任何编程风格,其中您的程序是问题或解决方案的描述 - 但 doesn't explicitly state how the work will be done .
Functional Programming 是通过评估函数的函数和函数进行编程...正如(严格定义的)函数式编程意味着通过定义无副作用的自由数学函数进行编程,因此 is a form of declarative programming but it isn't the only kind of declarative programming .
声明性语言通常被指定为'what must be done',而不是'how to do it',我认为这是一个误称,声明性程序仍然指定必须如何从输入到输出,但另一方面,您指定的关系必须是有效可计算的(重要术语)如果你不知道,请查阅它 . 另一种方法是非确定性编程,它实际上只是指定了什么条件结果很多,在你的实现之前,只是在试验和错误之前耗尽所有路径,直到它成功 .
14 回答
在撰写本文时,此页面上的最高投票答案在声明性与命令性定义上是不精确和混淆的,包括引用维基百科的答案 . 一些答案以不同的方式混淆了这些术语 .
另请参阅my explanation,为什么电子表格编程是声明性的,无论公式如何改变单元格 .
此外,一些答案声称函数式编程必须是声明性的子集 . 在这一点上,它取决于我们将“功能”与“程序”区分开来 . 让我们首先处理命令式与声明性 .
Definition of declarative expression
可能将声明性表达式与命令式表达式区分开的 only 属性是其子表达式的引用透明度(RT) . 所有其他属性在两种类型的表达式之间共享,或者从RT派生 .
100%声明性语言(即其中每个可能的表达式都是RT)不会(在其他RT要求中)允许存储值的突变,例如, HTML和大多数Haskell .
Definition of RT expression
RT通常被称为"no side-effects" . 术语效果没有精确的定义,因此有些人不同意"no side-effects"与RT相同 . RT有precise definition .
由于每个子表达式在概念上都是函数调用,因此RT要求函数的实现(即被调用函数内的表达式)可能无法访问函数外部的可变状态(允许访问mutable local state) . 简而言之,功能(实现)应该是纯粹的 .
Definition of pure function
一个纯粹的功能通常被认为是"no side-effects" . 效果一词没有准确的定义,所以有些人不同意 .
纯函数具有以下属性 .
唯一可观察的输出是返回值 .
唯一的输出依赖是参数 .
在生成任何输出之前,
参数已完全确定 .
请记住,RT适用于表达式(包括函数调用),纯度适用于(函数的实现) .
使用RT表达式的不纯函数的一个模糊示例是并发的,但这是因为在中断抽象层中纯度被破坏了 . 你真的不需要知道这一点 . 要制作RT表达式,可以调用纯函数 .
Derivative attributes of RT
用于声明性编程的任何其他属性,例如维基百科使用的citation from 1999既可以从RT派生,也可以与命令式编程共享 . 从而证明我的精确定义是正确的 .
注意,external values的不变性是RT要求的一个子集 .
声明性语言没有循环控制结构,例如
for
和while
,因为 due to immutability ,循环条件永远不会改变 .声明性语言不表示嵌套函数顺序(a.k.a逻辑依赖关系)以外的控制流,因为 due to immutability ,评估顺序的其他选择不会更改结果(参见下文) .
声明性语言表示逻辑"steps"(即嵌套的RT函数调用顺序),但是每个函数调用是否是更高级别的语义(即"what to do")不是声明性编程的要求 . 与命令的区别在于(即更一般地为RT),这些"steps"不能依赖于可变状态,而是仅依赖于表达逻辑的关系顺序(即函数调用的嵌套顺序,即a.k.a.子表达式) .
例如,在评估段落中的子表达式(即标记)之前,不能显示HTML段落
<p>
. 由于标记层次结构的逻辑关系(子表达式的嵌套,它们是analogously nested function calls),因此没有可变状态,只有顺序依赖性 .Evaluation order
当任何函数调用不是RT(即函数不是纯粹的)时,子表达式的评估顺序的选择只能给出变化的结果,例如,在函数内访问函数外部的一些可变状态 .
例如,给定一些嵌套表达式,例如
f( g(a, b), h(c, d) )
,如果函数f
,g
和h
是纯的,那么对函数参数的急切和懒惰评估将给出相同的结果 .然而,如果函数
f
,g
和h
不是纯粹的,那么评估顺序的选择可以给出不同的结果 .注意,嵌套表达式是概念上嵌套的函数,因为表达式运算符只是伪装成一元前缀,一元后缀或二进制中缀表示法的函数调用 .
切线,如果所有标识符,例如,
a
,b
,c
,_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _顺便说一下,Haskell有不同的语法,
f (g a b) (h c d)
.Evaluation order details
函数是从输入到输出的状态转换(不是可变的存储值) . 对于调用纯函数的RT组合,这些状态转换的执行顺序是独立的 . 由于缺乏副作用和RT function may be replaced by its cached value原则,每个函数调用的状态转换独立于其他函数调用 . 至于correct a popular misconception,纯粹的monadic组成是 always declarative and RT ,尽管Haskell的
IO
monad是arguably impure,因此势在必行w.r.t.程序外部的World
状态(但在下面的警告意义上,副作用是孤立的) .急切评估意味着在调用函数之前评估函数参数,延迟评估意味着在函数内访问它们(以及它们) .
Definition :函数参数在函数定义站点声明,函数调用在函数调用站点提供 . 知道参数和参数之间的区别 .
从概念上讲,所有表达式都是(一个组合的)函数调用,例如常量是没有输入的函数,一元运算符是具有一个输入的函数,二进制中缀运算符是具有两个输入的函数,构造函数是函数,甚至控制语句(例如
if
,for
,while
)可以用函数建模 . 评估的order that these参数函数(不要与嵌套函数调用顺序混淆)不是由语法声明的,例如f( g() )
可以急切地评估g
然后f
对g
的结果,或者它可以评估f
并且仅在f
内需要其结果时懒惰地评估g
.警告,没有Turing complete语言(即允许无限递归)完全是声明性的,例如,懒惰评估引入记忆和时间不确定性 . 但是由于评估顺序的选择而产生的这些副作用仅限于内存消耗,执行时间,延迟,非终止和外部同步 .
Functional programming
因为声明性编程不能有循环,所以迭代的唯一方法是函数递归 . 从这个意义上讲,函数式编程与声明式编程有关 .
但 functional programming is not limited to declarative programming . 功能组成可以是contrasted with subtyping,尤其是对于Expression Problem,其中扩展可以通过either adding subtypes or functional decomposition实现 . Extension can be a mix两种方法 .
函数式编程通常使函数成为第一类对象,这意味着函数类型可以出现在任何其他类型的语法中 . 结果是函数可以输入和操作函数,从而通过强调函数组合来提供关注点分离,即分离确定性计算的子计算之间的依赖性 .
例如,对于可以应用于集合的每个元素的无数个可能的专用动作中的每一个,函数编程采用可重复使用的迭代函数,例如,instead of writing a separate function(并且如果函数也必须是声明的,则使用递归而不是循环) .
map
,fold
,filter
. 这些迭代函数输入一流的专用动作函数 . 这些迭代函数迭代集合并为每个元素调用输入专用操作函数 . 这些操作函数更简洁,因为它们不再需要包含循环语句来迭代集合 .但是,请注意,如果函数不纯,那么它实际上是一个过程 . 我们或许可以争辩说使用不纯函数的函数式编程实际上是程序式编程 . 因此,如果我们同意声明表达式是RT,那么我们可以说程序编程不是声明性编程,因此我们可能会认为函数式编程总是RT并且必须是声明性编程的子集 .
Parallelism
具有一等函数的这种功能组合可以通过分离出独立的功能来表达depth in the parallelism .
并发性和并行性也是require declarative programming,即不变性和RT .
FP评估订单
请注意,评估顺序还会影响功能组合的终止和性能副作用 .
渴望(CBV)和懒惰(CBN)是分类决斗[10],因为它们具有相反的评估顺序,即分别是首先评估外部函数还是内部函数 . 想象一个倒置的树,然后急切地从功能树分支提示分支层次结构到顶级功能主干;然而,懒惰评估从树干到分支提示 . 渴望没有分离的副产品("or",a / k / a分类"sums")[11] .
Performance
与非终止一样,渴望过于渴望联合功能组合,即组合控制结构做了不必要的懒惰工作 . 对于example,当它由一个终止于第一个真元素的折叠组成时,急切地并且不必要地将整个列表映射到布尔值 .
这种不必要的工作是在渴望与懒惰的连续时间复杂性中所声称的额外log n factor的原因,两者都具有纯粹的功能 . 一种解决方案是使用具有惰性构造函数的函子(例如列表)(即,渴望使用可选的惰性产品),因为急切地渴望不正确性源于内部函数 . 这是因为产品是建设性的类型,即在初始定点上具有初始代数的归纳类型[11]
与非终止一样,懒惰过于懒惰,具有析取的功能组成,即共同结束可能发生在必要的晚期之后,导致不必要的工作和迟到的非确定性,而不是急切的[10] [11] . 最终的例子是状态,定时,非终止和运行时异常 . 这些是必要的副作用,但即使在纯粹的声明性语言(例如Haskell)中,在命令式IO monad中存在状态(注意:并非所有monad都是命令性的!)隐含在空间分配中,而时序是相对于命令式的状态真实世界 . 使用lazy甚至使用可选的热切副产品会泄漏"laziness"到内部副产品中,因为懒惰的懒惰不正确originates from the outer function(参见非终止部分中的示例,其中==是外部二元运算符函数) . 这是因为副产品受到终结性的限制,即在最终对象上具有最终代数的共同类型[11] .
懒惰导致延迟和空间函数的设计和调试中的不确定性,其调试可能超出了大多数程序员的能力,因为dissonance between声明了函数层次结构和运行时评估顺序 . 使用eager评估的惰性纯函数可能会在运行时引入以前看不见的非终止 . 相反,使用lazy评估的热切纯函数可能会在运行时引入以前看不见的空间和延迟不确定性 .
Non-termination
在编译时,由于Halting问题和Turing完整语言中的相互递归,通常不能保证函数终止 .
对于
Head
"and"Tail
的结合,如果Head
或Tail
没有终止,则分别要么List( Head(), Tail() ).tail == Tail()
或List( Head(), Tail() ).head == Head()
不为真,因为左侧不是,而右侧是,终止 .然而,懒惰双方终止 . 因此,渴望对联合产品过于渴望,并且在没有必要的情况下非终止(包括运行时异常) .
对于
1
"or"2
的分离,如果1
"or"2
没有终止,那么List( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tail
不是真的,因为左侧终止,而右侧不终止 .然而,由于渴望双方都不会终止所以从未达到过平等测试 . 因此懒惰对于析取副产品来说太懒惰,并且在那些情况下,在做了比热切的工作更多的工作之后未能终止(包括运行时异常) .
[10]陈述性延续和分类对偶性,Filinski,第2.5.4节CBV和CBN的比较,以及SCL中的3.6.1 CBV和CBN .
[11]声明性延续和分类二元性,Filinski,第2.2.1节产品和副产品,2.2.2终端和初始对象,2.5.2具有惰性产品的CBV,以及具有急切副产品的2.5.3 CBN .
对于这些,没有任何非模棱两可的客观定义 . 我将如何定义它们:
Imperative - 重点是计算机应采取的步骤而不是计算机将采取的步骤(例如C,C,Java) .
Declarative - 重点是计算机应该做什么而不是应该如何做(例如SQL) .
Functional - 一种重要关注递归的声明性语言的子集
imperative 和 declarative 描述了两种相反的编程风格 . 命令式是传统的"step by step recipe"方法,而声明性更为"this is what i want, now you work out how to do it" .
这两种方法贯穿整个编程 - 即使使用相同的语言和相同的程序 . 通常,声明性方法被认为是优选的,因为它使程序员不必指定如此多的细节,同时也有更少的机会获得错误(如果你描述了你想要的结果,并且一些经过良好测试的自动过程可以从那个向后工作定义步骤然后你可能希望事情比必须手动指定每个步骤更可靠 .
另一方面,一种命令式方法可以让您进行更低级别的控制 - 这是编程的“微管理方法” . 这可以让程序员利用有关问题的知识来提供更有效的答案 . 因此,程序的某些部分以更具说明性的方式编写并不罕见,但速度关键部分更为必要 .
正如您可能想象的那样,您用来编写程序的语言会影响您的声明性 - 一种内置“智能”的语言,用于计算结果,给出对结果的描述将允许更多的声明程序员需要首先在命令式代码中添加这种智能才能在顶部构建更具声明性的层 . 因此,例如,像prolog这样的语言被认为是非常具有声明性的,因为它内置了一个搜索答案的过程 .
到目前为止,你提到 functional 编程 . 's because it'这个词的含义与其他两个词没有直接关系 . 最简单的函数式编程意味着您可以使用函数 . 特别是,您使用支持函数的语言"first class values" - 这意味着您不仅可以编写函数,还可以编写函数来编写函数(编写函数......),并将函数传递给函数 . 简而言之 - 功能与字符串和数字一样灵活和通用 .
然后,通常会一起提到功能性,命令性和声明性 . 其原因是将功能编程理念“推向极端” . 从最纯粹的意义上讲,函数是来自数学的东西 - 一种“黑盒子”,它接受一些输入并始终提供相同的输出 . 而且这种行为不需要存储变化的变量 . 因此,如果你设计一种编程语言,其目的是实现一种非常纯粹的,数学上受影响的函数式编程思想,那么你最终会主要拒绝可以改变的 Value 观(在一定的,有限的,技术意义上) .
如果你这样做 - 如果你限制变量的变化 - 那么几乎是偶然的,你最终强迫程序员编写更具说明性的程序,因为命令式编程的很大一部分是描述变量如何变化,你不能再那样做了!事实证明,函数式编程 - 特别是函数式编程 - 往往会提供更多的声明性代码 .
总结一下,然后:
命令式和声明性是两种相反的编程风格(相同的名称用于鼓励这些风格的编程语言)
函数式编程是一种编程风格,其中函数变得非常重要,因此,更改值变得不那么重要 . 指定值更改的能力有限会强制使用更具说明性的样式 .
所以“函数式编程”通常被描述为“声明式” .
简而言之:
imperative language 指定计算机按顺序执行的一系列指令(执行此操作,然后执行此操作) .
declarative language 声明了一组关于哪些输出应该来自哪些输入的规则(例如,如果你有A,那么结果是B) . 引擎会将这些规则应用于输入,并提供输出 .
functional language 声明了一组数学/逻辑函数,用于定义输入如何转换为输出 . 例如 . f(y)= y * y . 它是一种声明性语言 .
势在必行: how 实现我们的目标
声明: what 我们要实现
Imperative Programming 表示程序结构的任何编程风格 describing how the operations performed by a computer will happen .
Declarative Programming 表示任何编程风格,其中您的程序是问题或解决方案的描述 - 但 doesn't explicitly state how the work will be done .
Functional Programming 是通过评估函数的函数和函数进行编程...正如(严格定义的)函数式编程意味着通过定义无副作用的自由数学函数进行编程,因此 is a form of declarative programming but it isn't the only kind of declarative programming .
Logic Programming (例如在Prolog中)是另一种形式的声明性编程 . 它涉及通过判断逻辑语句是否为真(或者是否可以满足)来进行计算 . 该程序通常是一系列事实和规则 - 即描述而不是一系列指令 .
Term Rewriting (例如CASL)是另一种形式的声明性编程 . 它涉及代数术语的象征性转换 . 它完全不同于逻辑编程和函数式编程 .
imperative - 表达式描述要执行的操作序列(关联)
declarative - 表达式是有助于程序行为的声明(关联,交换,幂等,单调)
functional - 表达式只有效果值;语义支持等式推理
自从我写完之前的答案以来,我已经制定了一个new definition的声明属性,引用如下 . 我还将命令式编程定义为双重属性 .
这个定义优于我在之前的答案中提供的定义,因为它简洁而且更通用 . 但这可能更难以理解,因为适用于编程和生活的不完备性定理的含义对于人类来说难以包裹他们的思想 .
引用的定义解释讨论了纯函数式编程在声明性编程中的作用 .
所有奇异类型的编程都符合以下声明与命令的分类,因为以下定义声称它们是双重的 .
编辑:我将following comment发布到Robert Harper的博客:
命令式编程:告诉“机器”如何做某事,结果你想要发生的事情就会发生 .
声明性编程:告诉“机器”你想要发生什么,让计算机弄清楚如何去做 .
势在必行的例子
声明性示例
注意:区别不在于简洁,复杂或抽象 . 如上所述,区别在于如何与什么相比 .
如今,新的焦点:我们需要旧的分类?
过去的命令/声明/功能方面很好地对通用语言进行了分类,但是现在所有"big language"(如Java,Python,Javascript等)都有一些选项(通常是frameworks)用"other focus"来表达而不是它的主要语言(通常是frameworks)命令式),表达并行进程,声明性函数,lambdas等 .
所以这个问题的一个很好的变体就是"What aspect is good to classify frameworks today?" ......一个重要的方面是我们可以标记"programming style" ...
专注于数据与算法的融合
一个很好的例子来解释 . 你可以read about jQuery at Wikipedia,
因此,jQuery是关注"new programming style"的最佳(流行)示例,不仅是面向对象,而是“面向面向对象的", is "面向DOM节点”......在此上下文中比较主要样式:
No fusion :在所有"big languages"中,在任何功能/声明/命令式表达式中,通常是"no fusion"的数据和算法,除了一些面向对象,这是strict algebric structure观点的融合 .
Some fusion :所有经典的融合策略,在今天都有一些框架使用它作为范例... dataflow,_ 1188092(或旧域特定语言为awk和XSLT)...就像使用现代电子表格编程一样,它们也是 reactive programming 的例子样式 .
Big fusion :是"the jQuery style" ... jQuery是一种专注于“融合算法和DOM-data-structures”的领域专用语言 .
PS:其他"query languages",作为XQuery,SQL(PL作为命令式表达式选项)也是数据算法融合示例,但它们是孤岛,没有与其他系统模块融合... Spring,当使用
find()
-variants和Specification时条款,是另一个很好的融合例子 .声明性编程是通过在输入和输出之间表达一些永恒逻辑来编程,例如,在伪代码中,以下示例将是声明性的:
我们在这里定义一个称为“阶乘”的关系,并将输出和输入之间的关系定义为该关系 . 这里应该很明显,关于任何结构化语言都允许在某种程度上进行声明性编程 . 声明性编程的一个中心思想是不可变数据,如果你分配给一个变量,你只会这样做一次,然后再也不会 . 其他更严格的定义意味着可能根本没有副作用,这些语言有时被称为“纯粹的声明性” .
命令式风格的相同结果将是:
在这个例子中,我们在输入和输出之间没有表达永恒的静态逻辑关系,我们手动更改了内存地址,直到其中一个保持了所需的结果 . 显而易见的是,所有语言在某种程度上都允许声明性语义,但并非所有语言都允许使用命令式,一些“纯粹”声明性语言允许副作用和完全变异 .
声明性语言通常被指定为'what must be done',而不是'how to do it',我认为这是一个误称,声明性程序仍然指定必须如何从输入到输出,但另一方面,您指定的关系必须是有效可计算的(重要术语)如果你不知道,请查阅它 . 另一种方法是非确定性编程,它实际上只是指定了什么条件结果很多,在你的实现之前,只是在试验和错误之前耗尽所有路径,直到它成功 .
纯粹的声明性语言包括Haskell和Pure Prolog . 从一个到另一个的滑动比例将是:Pure Prolog,Haskell,OCaml,Scheme / Lisp,Python,Javascript,C--,Perl,PHP,C,Pascall,C,Fortran,Assembly
这里有一些关于注意到的“类型”的好答案 .
我提交了一些通常与功能编程人群相关的其他更“异国情调”的概念:
Domain Specific Language 或 DSL 编程:创建一种新语言来处理手头的问题 .
Meta-Programming :程序写入其他程序时 .
Evolutionary Programming :您构建一个不断改进自身或连续生成更好的子程序的系统 .
我认为您的分类法不正确 . 命令式和声明性有两种相反的类型 . 功能只是声明的一个子类型 . 顺便说一句,维基百科说明了同样的事实 .
简而言之,编程风格越强调什么(要做)抽象出如何(做到这一点)的细节越多,该风格被认为是声明性的 . 对于命令而言则恰恰相反 . 函数式编程与声明式样式相关联 .