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

function,声明和命令式编程是什么意思?

在写这篇文章的时候,这个页面上的最高投票答案在声明式和命令式的定义上是不精确和混乱的,包括引用维基百科的答案。 有些答案是用不同的方式来混合术语。

无论公式如何变更单元格,请参阅我为何解释电子表格程序的声明。

另外,有几个答案声称函数式编程必须是声明式的一个子集。 在这一点上,这取决于我们是否将“function”与“程序”区分开来。 让我们首先处理命令​​式和声明式。

声明式expression的定义

唯一可能区分声明式expression式和命令式expression式的属性是其子expression式的参考透明度(RT)。 所有其他属性或者在两种expression式之间共享,或者从RT派生。

一个100%的陈述性语言(即其中每一个可能的expression式都是RT的语言)不会(在其他的RT要求中)允许存储值的变化,例如HTML和Haskell的大部分。

RTexpression的定义

RT通常被称为“没有副作用”。 术语效应没有一个确切的定义,所以有些人不同意RT的“无副作用”。 RT有一个精确的定义 。

由于每个子expression式在概念上都是一个函数调用,因此RT要求函数的实现(即被调用函数内的expression式)不能访问函数外部的可变状态(访问可变局部状态为允许)。 简而言之,函数(实现)应该是纯粹的

纯函数的定义

一个纯粹的function通常被认为是“没有副作用”。 术语效果没有一个确切的定义,所以有些人不同意。

纯函数具有以下属性。

  • 唯一可观察的输出是返回值。
  • 唯一的输出依赖是参数。
  • 参数在任何输出生成之前完全确定。

请记住,RT适用于expression式(包括函数调用),纯度适用于(函数的)实现。

制作RTexpression式的不纯function的一个不为人知的例子是并发性,但这是因为中断抽象层的纯度被破坏了。 你不需要知道这一点。 要制作RTexpression式,可以调用纯函数。

RT的派生属性

为声明性编程引用的任何其他属性,例如维基百科使用的1999年的引用 ,要么来自RT,要么与命令式编程共享。 从而certificate我的确切定义是正确的。

请注意, 外部值的不变性是RT要求的一个子集。

  • 声明性语言没有循环控制结构,例如forwhile ,因为由于不变性 ,循环条件永远不会改变。

  • 声明性语言除了嵌套函数顺序(又称逻辑依赖性)之外不表示控制stream,因为由于不可变性 ,其他select的评估顺序不会改变结果(见下文)。

  • 声明性语言expression逻辑“步骤”(即嵌套的RT函数调用顺序),但是每个函数调用是否是更高级别的语义(即“做什么”)不是声明性编程的要求。 与命令的区别在于, 由于不变性 (即更一般的RT),这些“步骤”不能依赖于可变状态,而只能依赖expression逻辑的关系顺序(即函数调用的嵌套顺序,也就是子expression式)。

    例如,只有在段落中的子expression式(即标签)被评估之后,才能显示HTML段落<p> 。 由于标签层次结构的逻辑关系(子expression式嵌套 , 类似嵌套的函数调用 ),不存在可变状态,只有顺序依赖性。

  • 因此存在不变性(更一般的RT)的派生属性,即声明性expression式表示组成部分(即子expression式函数参数)的逻辑关系而不表示可变状态关系。

评估顺序

当任何函数调用不是RT时(即函数不是纯粹的),子expression式的评估顺序的select只能给出不同的结果,例如在函数内部访问函数外部的某个可变状态。

例如,给定一些嵌套expression式,例如f( g(a, b), h(c, d) ) ,如果函数fgh是纯的,那么对函数参数的急切和懒惰的评估将得到相同的结果。

而如果函数fgh不是纯粹的,则评估顺序的select可以给出不同的结果。

请注意,嵌套expression式是概念上嵌套的函数,因为expression式运算符只是伪装成一元前缀,一元后缀或二元中缀表示法的函数调用。

相切地,如果所有的标识符(例如abcd )在任何地方都是不可变的 ,程序外部的状态就不能被访问(即I / O),并且没有抽象层破损,那么函数总是纯粹的。

顺便说一下,Haskell具有不同的语法, f (gab) (hcd)

评估订单详情

函数是从input到输出的状态转换(而不是可变的存储值)。 对于调用函数的RT组合,这些状态转换的执行顺序是独立的。 每个函数调用的状态转换与其他函数调用的状态转换是独立的,由于缺乏副作用和RT函数可能被其caching值取代的原则。 为了纠正一个stream行的误解 ,尽pipeHaskell的IO monad 可以说是不纯的,并且对于程序外部的World状态是必须的 ,但是纯粹的monadic组合总是声明式和RT的(事实上,从下面的警告来看, – 效果是孤立的)。

急切的评价意味着函数参数在函数被调用之前被评估,而懒惰评估意味着直到(和如果)在函数内访问参数才被评估 。

定义 :函数参数在函数定义站点声明,函数参数在函数调用站点提供。 知道参数参数的区别。

从概念上讲,所有的expression式都是函数调用的组成部分,例如常量是没有input的函数,一元运算符是一个input的函数,二进制中缀运算符是两个input的函数,构造函数是函数,甚至是控制语句, while )可以用functionbuild模。 这些 参数函数的顺序 (不要与嵌套的函数调用顺序混淆)不会被语法声明,例如f( g() )可能会急于评估g然后f的结果,或者它只能评估ff内需要结果时,懒散地评估g

警告,没有图灵完整的语言(即允许无限recursion)是完全的说明性的,例如懒惰评估引入记忆和时间不确定性。 但是由于评估顺序的select,这些副作用仅限于内存消耗,执行时间,延迟,非终止和外部滞后以及外部同步。

function编程

因为声明式编程不能有循环,所以迭代的唯一方法就是函数recursion。 在这个意义上说,函数式编程与声明式编程有关。

但是函数式编程不限于声明式编程 。 function组成可以与子types相对照 ,特别是关于expression问题 ,其中扩展可以通过添加子types或function分解来实现。 扩展可以是两种方法的混合 。

函数式编程通常使函数成为第一类对象,这意味着函数types可以在任何其他types的语法中出现在语法中。 其结果是function可以input和操作function,从而通过强调function组合来提供关注分离,即分离确定性计算的子计算之间的依赖关系。

例如,函数式编程采用可重复使用的迭代, 而不是编写一个单独的函数 (如果函数也必须是声明式的,则使用recursion而不是循环),而无限次数的可能专用动作可以应用于集合的每个元素function,例如mapfoldfilter 。 这些迭代函数input一个一stream的专用动作函数。 这些迭代函数迭代集合并为每个元素调用input的专用操作函数。 这些动作函数更简洁,因为它们不再需要包含循环语句来迭代集合。

但是请注意,如果一个函数不是纯粹的,那么它确实是一个过程。 我们也许可以争辩说,使用不纯function的函数式编程实际上是程序性编程。 因此,如果我们同意声明性expression式是RT,那么我们可以说程序性编程不是声明性编程,因此我们可能会认为函数式编程总是RT,并且必须是声明性编程的一个子集。

排比

具有一streamfunction的function组合可以通过分离出独立的function来expression平行度的深度 。

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

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

那么Parallelism == Concurrency来自哪个危险的假设呢? 这是带有副作用的语言的一个自然结果:当你的语言到处都有副作用时,任何时候当你尝试做多个事情时,你基本上都会由于每个操作产生的交叉影响而产生非确定性。 所以在副作用语言中,获得并行性的唯一方法是并发性; 因此我们经常看到两者混淆并不奇怪。

FP评估顺序

注意评估顺序也影响function组合的终止和性能副作用。

渴望(CBV)和懒惰(CBN)是类别决斗[ 10 ],因为它们颠倒了评估顺序,即是否分别评估外部或内部function。 设想一个颠倒的树,然后从function树分支进行评估,提升分支层次到顶层function主干; 而懒惰评估从树干到分支提示。 渴望没有连接产品(“和”,a / k / a分类的“产品”)和懒惰不具有分离的副产品(“或”,a / k / a分类“总和”)。

性能

  • 急于

    与不终止一样,渴望过于渴望联结的function组成,即组成控制结构做不必要的工作,而不是懒惰的工作。 例如 ,当用一个以第一个真实元素结尾的折叠组成的整个列表时,热切地,不必要地将整个列表映射到布尔值。

    这种不必要的工作是声称的“达到”一个额外的日志因素在渴望与懒惰的连续时间复杂性,都与纯函数。 一个解决scheme是用懒惰的构造函数(即渴望可选的懒惰产品)使用函子(例如列表),因为渴望的不正确性源于内部函数。 这是因为产品是具有build设性的types,即在初始定点上具有初始代数的归纳types[ 11 ]

  • 与非终止一样,懒惰与分离的function组成也是懒惰的,也就是说,共诱导终结可能晚于必要发生,导致迟到的不必要的工作和非确定性,而不是急于求助[ 10 ] [ 11 ] 。 终结的例子是状态,时间,非终止和运行时exception。 这些都是必要的副作用,但即使在纯粹的声明性语言(如Haskell)中,在命令式IO单子中也存在状态(注意:并非所有的单子都是命令性的),而且时间是相对于命令真实世界。 即使使用可选的保留副本,懒惰也会将“laziness”泄漏到内部副本中,因为懒惰不正确来自外部函数 (请参见Non-termination部分中的示例,其中==是外部二元运算符函数)。 这是因为副产品是有限的,即最终代数上的最后一个代数的共诱导types[ 11 ]。

    由于所声明的函数层次结构与运行时评估顺序之间的不一致 ,因此懒惰会导致对延迟和空间函数的devise和debugging的不确定性,其debugging可能超出大多数程序员的能力。 惰性计算的懒惰纯函数可能会在运行时引入以前看不到的非终止。 相反,用惰性来评估急切的纯函数可能会在运行时潜在地引入先前看不见的空间和延迟不确定性。

未结束

在编译时,由于Haling问题和图灵完备语言中的相互recursion,函数通常不能保证终止。

  • 急于

    如果HeadTail不结尾,那么分别使用List( Head(), Tail() ).tail == Tail()List( Head(), Tail() ).head == Head()不是真的,因为左侧没有,右侧是终止。

    而懒惰双方终止。 因此,渴望对于连接产品过于渴望,并且在没有必要的情况下不终止(包括运行时exception)。

  • 对于12 List( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tail ,如果f不终止,则List( f ? 1 : 2, 3 ).tail == (f ? List( 1, 3 ) : List( 2, 3 )).tail是不正确的,因为左侧终止,右侧不是。

    而急切地,双方终止,平等testing永远不会到达。 因此,惰性对于不连续的副产品来说太懒,在这些情况下,在做了比渴望更多的工作之后,无法终止(包括运行时exception)。

[ 10 ]声明性连续性和分类对偶性,Filinski,章节2.5.4 CBV和CBN的比较,以及3.6.1 SCL中的CBV和CBN。

[ 12 ]声明性延续和分类对偶,Filinski,2.2.1节2.2.1产品和副产品,2.2.2terminal和初始对象,2.5.2 CBV带有懒惰产品,2.5.3 CBN带有渴望的副产品。

这些没有任何非模棱两可的客观定义。 以下是将如何定义它们:

势在必行 – 重点在于计算机应采取的步骤,而不是计算机将执行什么操作 (例如C,C ++,Java)。

声明 – 重点是计算机应该做什么,而不是它应该如何做(如SQL)。

function性 – 重点关注recursion的声明性语言的子集

命令性的陈述性的描述了两种相反的编程风格。 势在必行的是传统的“一步一步的配方”方法,而陈述式更多的是“这就是我想要的,现在你正在研究如何去做”。

这两种方法在整个编程过程中都会发生 – 即使使用相同的语言和相同的程序。 通常声明式方法被认为是可取的,因为它使程序员不必指定如此多的细节,同时也减less了错误的机会(如果你描述了你想要的结果,而且一些经过充分testing的自动化过程可以从这个方法向后定义步骤,那么你可能希望事情比必须手动指定每一步更可靠)。

另一方面,一个必要的方法给你更低层次的控制 – 这是编程的“微观pipe理方法”。 这可以让程序员利用有关问题的知识来给出更有效的答案。 所以一个程序的某些部分被写成更具说明性的风格并不罕见,但对速度至关重要的部分则更为重要。

正如你所想象的那样,你用来编写程序的语言会影响到你是如何声明的 – 一种内置“智能”的语言用于处理结果的描述,从而允许更多的声明而不是程序员需要首先在命令式代码中添加这种智能的方法,然后才能在顶层构build一个更具说明性的层。 所以,比如,像prolog这样的语言被认为是非常具有说服力的,因为它内置了一个search答案的过程。

到目前为止,你会注意到我没有提到函数式编程。 那是因为这是一个术语,其含义与其他两个并不直接相关。 在其最简单的function编程意味着你使用的function。 特别是使用支持函数的语言作为“第一类值” – 这意味着您不仅可以编写函数,还可以编写写函数的函数(写函数…),并将函数传递给function。 简而言之,这些function就像string和数字一样灵活而通用。

那么,function性,必要性和陈述性往往会一起被提及,这似乎很奇怪。 其原因是将函数式编程思想“推向极致”的结果。 从最纯粹的意义上讲,函数是math的东西 – 一种“黑匣子”,它需要一些input,总是给出相同的输出。 这种行为不需要存储变化的variables。 所以如果你devise一个编程语言,其目的是实现一个非常纯粹的,math上受到math影响的函数式编程思想,那么你最终会在很大程度上拒绝可以改变的价值观念(在某种有限的技术意义上)。

如果你这样做 – 如果你限制variables如何改变 – 那么几乎是偶然的,最终你会迫使程序员编写更具说明性的程序,因为大部分命令式编程正在描述variables是如何变化的,而且你不能再去做! 所以事实certificate,函数式编程 – 特别是用函数式语言编程 – 往往会给出更多的声明式代码。

总结一下,那么:

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

  • 函数式编程是function变得非常重要的一种编程风格,因此,变化的价值变得不那么重要。 指定值更改的能力有限会强制更具声明性的风格。

所以“function性程序devise”通常被描述为“声明性”。

简而言之:

命令式语言规定了计算机按顺序执行的一系列指令(执行此操作,然后执行该操作)。

声明性语言声明了一组关于哪些输出应该从哪个input中得到的规则(例如,如果你有A,那么结果是B)。 引擎将这些规则应用于input,并给出输出。

一个函数式语言声明了一组math/逻辑函数,它们定义了如何将input转换为输出。 例如。 f(y)= y * y。 它是一种声明性语言。

势在必行: 如何实现我们的目标

  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 

命令式编程意味着任何编程风格,你的程序是由描述如何由计算机执行的操作的指令构成的。

声明式编程是指任何编程风格,其中程序是描述问题或解决scheme – 但没有明确说明工作将如何完成

函数式编程是通过评估函数的函数和函数来进行编程的。作为(严格定义的)函数式编程意味着通过定义无副作用的math函数来进行编程,所以它是一种声明式编程forms,但它不是唯一的声明式编程

逻辑编程 (例如在Prolog中)是声明式编程的另一种forms。 它涉及通过判断逻辑陈述是否真实(或者是否可以满足)来进行计算。 该程序通常是一系列的事实和规则 – 即描述而不是一系列指令。

术语重写 (例如CASL)是声明性编程的另一种forms。 它涉及代数术语的符号转换。 它与逻辑编程和function编程完全不同。

命令式 – expression式描述要执行的动作序列(关联式)

声明性expression式是对程序行为有贡献的声明(关联性,交换性,幂等性,单调性)

function – expression只有效果才有价值 ; 语义支持等式推理

自从我写了以前的答案以来,我已经在下面引用了一个新的声明性的定义 。 我也将命令式编程定义为双重属性。

这个定义比我在前面的答案中提供的定义要好,因为它简洁而且更一般。 但是可能更难以理解,因为适用于编程和生活的不完备性定理的含义一般难以让人思考。

定义的引用解释讨论了函数式编程在声明式编程中的作用。

所有奇特types的编程都符合以下声明式和必要式的分类标准,因为下面的定义声称它们是双重的。

说明性与必要性

声明性属性是奇怪的,钝的,并且难以在技术上精确的定义中捕捉,这个定义仍然是一般的而不是含糊不清的,因为我们可以声明程序的含义(又名语义)而不会产生意想不到的副作用,这是一个天真的想法。 意义的expression与避免意想不到的效果之间存在内在的张力,而这种张力实际上是由程序和我们的宇宙的不完备定理导出的。

这是过于简单化,技术上不精确,而且往往模棱两可的定义为做什么如何做 ”的必要性。 一个模棱两可的情况是在输出一个程序的程序中“ 怎样 ”是一个编译器。

显然,使语言图灵完整的无限recursion也类似于语义 – 不仅在评估的语法结构(也就是操作语义)中。 这在逻辑上就是类似于哥德尔定理的一个例子 – “ 任何完整的公理系统也是不一致的 ”。 思考那个引用的矛盾奇怪! 这也是一个例子,说明了语义的expression如何不具有可certificate的界限,因此我们不能certificate程序(类似地,它的语义)停止了又叫停止定理。

不完全性定理是从我们的宇宙的基本性质中得出的,正如热力学第二定律所说的那样:“ (又名独立可能性) 正趋向于永远的最大值 ”。 程序的编码和devise从来没有完成 – 它还活着! – 因为它试图解决现实世界的需要,现实世界的语义总是在变化,并趋向于更多的可能性。 人类永远不会停止发现新事物(包括程序中的错误;-)。

为了精确地和技术上地捕捉到这个在没有边缘的怪异宇宙(思考这个!宇宙没有“外在”)内所期望的这个概念,需要一个简洁而非欺骗性的非简单的定义,直到解释深。

定义:


声明性属性是只能存在一组可能expression每个特定模块化语义的语句的地方。

命令性属性3是双重的,其中语义在构成下是不一致的,并且/或者可以用一组语句的变体来expression。


这种声明的定义在语义范围上是独特的局部的 ,这意味着它要求模块化的语义保持其一致的含义,而不pipe它在全球范围内如何实例化和使用。 因此,每个说明性的模块化语义应该与所有可能的其他语言本质上是正交的,而不是一个不可能的(由于不完备性定理) 全局algorithm或者一致性见证模型,这也是罗伯特·哈珀教授的“ 更多并不总是更好 ”卡内基梅隆大学计算机科学硕士,标准ML的devise师之一。

这些模块化的声明性语义的例子包括类别理论函子,例如Applicative ,名义types,命名空间,命名字段,以及操作级别的语义,然后是纯函数式编程。

因此,devise良好的陈述式语言可以更清楚地expression意义 ,尽pipe可以expression某种程度上的一般性损失,但是可以用内在的一致性来expression。

上述定义的一个例子是电子表格程序的单元格中的一组公式,当移动到不同的列和行单元格时,不期望赋予相同的含义,即单元格标识符改变。 小区标识符是预期含义的一部分而不是多余的。 因此,每个电子表格结果对于一组公式中的单元格标识符是唯一的。 在这种情况下,一致的模块化语义是使用单元格标识符作为单元格公式的函数的input和输出(见下文)。

超文本标记语言又名HTML–静态网页的语言 – 是一个高度(但不是完美的)声明性语言(至less在HTML 5之前)不能expressiondynamic行为的例子。 HTML也许是最容易学习的语言。 对于dynamic行为,JavaScript等命令式脚本语言通常与HTML结合使用。 没有JavaScript的HTML符合声明性的定义,因为每个名义types(即标签)在语法规则的组合下保持其一致的含义。

声明性的竞争定义是语义陈述的交换和幂等性,也就是说陈述可以在不改变含义的情况下重新sorting和重复。 For example, statements assigning values to named fields can be reordered and duplicated without changed the meaning of the program, if those names are modular wrt to any implied order. Names sometimes imply an order, eg cell identifiers include their column and row position— moving a total on spreadsheet changes its meaning. Otherwise, these properties implicitly require global consistency of semantics. It is generally impossible to design the semantics of statements so they remain consistent if randomly ordered or duplicated, because order and duplication are intrinsic to semantics. For example, the statements “Foo exists” (or construction) and “Foo does not exist” (and destruction). If one considers random inconsistency endemical of the intended semantics, then one accepts this definition as general enough for the declarative property. In essence this definition is vacuous as a generalized definition because it attempts to make consistency orthogonal to semantics, ie to defy the fact that the universe of semantics is dynamically unbounded and can't be captured in a global coherence paradigm.

Requiring the commutative and idempotent properties for the (structural evaluation order of the) lower-level operational semantics converts operational semantics to a declarative localized modular semantic, eg pure functional programming (including recursion instead of imperative loops). Then the operational order of the implementation details do not impact (ie spread globally into) the consistency of the higher-level semantics. For example, the order of evaluation of (and theoretically also the duplication of) the spreadsheet formulas doesn't matter because the outputs are not copied to the inputs until after all outputs have been computed, ie analogous to pure functions.

C, Java, C++, C#, PHP, and JavaScript aren't particularly declarative. Copute's syntax and Python's syntax are more declaratively coupled to intended results , ie consistent syntactical semantics that eliminate the extraneous so one can readily comprehend code after they've forgotten it. Copute and Haskell enforce determinism of the operational semantics and encourage “ don't repeat yourself ” (DRY), because they only allow the pure functional paradigm.


2 Even where we can prove the semantics of a program, eg with the language Coq, this is limited to the semantics that are expressed in the typing , and typing can never capture all of the semantics of a program— not even for languages that are not Turing complete, eg with HTML+CSS it is possible to express inconsistent combinations which thus have undefined semantics.

3 Many explanations incorrectly claim that only imperative programming has syntactically ordered statements. I clarified this confusion between imperative and functional programming . For example, the order of HTML statements does not reduce the consistency of their meaning.


Edit: I posted the following comment to Robert Harper's blog:

in functional programming … the range of variation of a variable is a type

Depending on how one distinguishes functional from imperative programming, your 'assignable' in an imperative program also may have a type placing a bound on its variability.

The only non-muddled definition I currently appreciate for functional programming is a) functions as first-class objects and types, b) preference for recursion over loops, and/or c) pure functions— ie those functions which do not impact the desired semantics of the program when memoized ( thus perfectly pure functional programming doesn't exist in a general purpose denotational semantics due to impacts of operational semantics, eg memory allocation ).

The idempotent property of a pure function means the function call on its variables can be substituted by its value, which is not generally the case for the arguments of an imperative procedure. Pure functions seem to be declarative wrt to the uncomposed state transitions between the input and result types.

But the composition of pure functions does not maintain any such consistency, because it is possible to model a side-effect (global state) imperative process in a pure functional programming language, eg Haskell's IOMonad and moreover it is entirely impossible to prevent doing such in any Turing complete pure functional programming language.

As I wrote in 2012 which seems to the similar consensus of comments in your recent blog , that declarative programming is an attempt to capture the notion that the intended semantics are never opaque. Examples of opaque semantics are dependence on order, dependence on erasure of higher-level semantics at the operational semantics layer (eg casts are not conversions and reified generics limit higher-level semantics ), and dependence on variable values which can not be checked (proved correct) by the programming language.

Thus I have concluded that only non-Turing complete languages can be declarative.

Thus one unambiguous and distinct attribute of a declarative language could be that its output can be proven to obey some enumerable set of generative rules. For example, for any specific HTML program (ignoring differences in the ways interpreters diverge) that is not scripted (ie is not Turing complete) then its output variability can be enumerable. Or more succinctly an HTML program is a pure function of its variability. Ditto a spreadsheet program is a pure function of its input variables.

So it seems to me that declarative languages are the antithesis of unbounded recursion , ie per Gödel's second incompleteness theorem self-referential theorems can't be proven.

Lesie Lamport wrote a fairytale about how Euclid might have worked around Gödel's incompleteness theorems applied to math proofs in the programming language context by to congruence between types and logic (Curry-Howard correspondence, etc).

Imperative programming: telling the “machine” how to do something, and as a result what you want to happen will happen.

Declarative programming: telling the “machine” what you would like to happen, and letting the computer figure out how to do it.

Example of imperative

 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; } 

Example of declarative

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

Note: The difference is not one of brevity or complexity or abstraction. As stated, the difference is how vs what .

Nowadays, new focus: we need the old classifications?

The Imperative/Declarative/Functional aspects was good in the past to classify generic languages, but in nowadays all "big language" (as Java, Python, Javascript, etc.) have some option (typically frameworks ) to express with "other focus" than its main one (usual imperative), and to express parallel processes, declarative functions, lambdas, etc.

So a good variant of this question is "What aspect is good to classify frameworks today?" … An important aspect is something that we can labeling "programming style"

Focus on the fusion of data with algorithm

A good example to explain. As you can read about jQuery at Wikipedia ,

The set of jQuery core features — DOM element selections, traversal and manipulation —, enabled by its selector engine (…), created a new "programming style", fusing algorithms and DOM-data-structures

So jQuery is the best (popular) example of focusing on a "new programming style" , that is not only object orientation, is " Fusing algorithms and data-structures ". jQuery is somewhat reactive as spreadsheets, but not "cell-oriented", is " DOM-node oriented "… Comparing the main styles in this context:

  1. No fusion : in all "big languages", in any Functional/Declarative/Imperative expression, the usual is "no fusion" of data and algorithm, except by some object-orientation, that is a fusion in strict algebric structure point of view.

  2. Some fusion : all classic strategies of fusion, in nowadays have some framework using it as paradigm… dataflow , Event-driven programming (or old domain specific languages as awk and XSLT )… Like programming with modern spreadsheets, they are also examples of reactive programming style.

  3. Big fusion : is "the jQuery style"… jQuery is a domain specific language focusing on " fusing algorithms and DOM-data-structures ".
    PS: other "query languages", as XQuery, SQL (with PL as imperative expression option) are also data-algorith-fusion examples, but they are islands , with no fusion with other system modules… Spring , when using find() -variants and Specification clauses, is another good fusion example.

Declarative programming is programming by expressing some timeless logic between the input and the output, for instance, in pseudocode, the following example would be declarative:

 def factorial(n): if n < 2: return 1 else: return factorial(n-1) output = factorial(argvec[0]) 

We just define a relationship called the 'factorial' here, and defined the relationship between the output and the input as the that relationship. As should be evident here, about any structured language allows declarative programming to some extend. A central idea of declarative programming is immutable data, if you assign to a variable, you only do so once, and then never again. Other, stricter definitions entail that there may be no side-effects at all, these languages are some times called 'purely declarative'.

The same result in an imperative style would be:

 a = 1 b = argvec[0] while(b < 2): a * b-- output = a 

In this example, we expressed no timeless static logical relationship between the input and the output, we changed memory addresses manually until one of them held the desired result. It should be evident that all languages allow declarative semantics to some extend, but not all allow imperative, some 'purely' declarative languages permit side effects and mutation altogether.

Declarative languages are often said to specify 'what must be done', as opposed to 'how to do it', I think that is a misnomer, declarative programs still specify how one must get from input to output, but in another way, the relationship you specify must be effectively computable (important term, look it up if you don't know it). Another approach is nondeterministic programming, that really just specifies what conditions a result much meet, before your implementation just goes to exhaust all paths on trial and error until it succeeds.

Purely declarative languages include Haskell and Pure Prolog. A sliding scale from one and to the other would be: Pure Prolog, Haskell, OCaml, Scheme/Lisp, Python, Javascript, C–, Perl, PHP, C++, Pascall, C, Fortran, Assembly

Some good answers here regarding the noted "types".

I submit some additional, more "exotic" concepts often associated with the functional programming crowd:

  • Domain Specific Language or DSL Programming: creating a new language to deal with the problem at hand.
  • Meta-Programming : when your program writes other programs.
  • Evolutionary Programming : where you build a system that continually improves itself or generates successively better generations of sub-programs.

I think that your taxonomy is incorrect. There are two opposite types imperative and declarative. Functional is just a subtype of declarative. BTW, wikipedia states the same fact.

In a nutshell, the more a programming style emphasizes What (to do) abstracting away the details of How (to do it) the more that style is considered to be declarative. The opposite is true for imperative. Functional programming is associated with the declarative style.