功能,声明和命令式编程这两个术语是什么意思?
#1楼
命令式 - 表达式描述要执行的动作序列(关联)
声明性 - 表达式是有助于程序行为的声明(关联,交换,幂等,单调)
功能 -词汇具有值作为唯一的影响; 语义支持等式推理
#2楼
简而言之,编程风格越强调什么(要做)抽象出如何(做到这一点)的细节越多,该风格被认为是声明性的。 对于命令而言则恰恰相反。 函数式编程与声明式样式相关联。
#3楼
自从我写完之前的答案以来,我已经制定了一个新的声明性属性定义 ,引用如下。 我还将命令式编程定义为双重属性。
这个定义优于我在之前的答案中提供的定义,因为它简洁而且更通用。 但这可能更难以理解,因为适用于编程和生活的不完备性定理的含义对于人类来说难以包裹他们的思想。
引用的定义解释讨论了纯函数式编程在声明性编程中的作用。
所有奇异类型的编程都符合以下声明与命令的分类,因为以下定义声称它们是双重的。
声明与命令
声明性属性是奇怪的,钝的,难以捕获的技术精确定义仍然是一般的而不是模糊的,因为我们可以声明程序的含义(也就是语义)而不会产生意想不到的副作用,这是一个天真的概念。 意义表达与避免意外效应之间存在着内在的张力,而这种紧张实际上源于编程和宇宙的不完备性定理 。
它过于简单化,技术上不精确,并且通常含糊不清地将声明定义为“ 做什么 ” ,将命令称为“ 如何做 ” 。 一个模糊的情况是“ 什么 ”是输出程序编译器的程序中的“ 如何 ”。
显然,使语言图灵完成的无界递归也类似于语义 - 不仅在评估的语法结构(又称操作语义)中。 这在逻辑上类似于哥德尔定理的例子 - “ 任何完整的公理系统也是不一致的 ”。 思考那句话的矛盾怪异! 它也是演示如何语义的表达不具有可证明的结合的一个例子,因此,我们也不能证明2 ,一个程序(和其类似语义)停止又名停机定理。
不完备性定理源于我们宇宙的基本性质,正如热力学第二定律所述,“ 熵 (又称独立可能性的#) 趋向于永远最大化 ”。 程序的编码和设计永远不会完成 - 它还活着! - 因为它试图满足现实世界的需要,现实世界的语义总是在变化,趋向于更多的可能性。 人类永远不会停止发现新事物(包括程序中的错误;-)。
为了在这个没有边缘的奇怪宇宙中精确地和技术上捕捉到上述理想的概念(思考宇宙中没有“外部”),需要一个简洁但看似不那么简单的定义,这个定义听起来不正确,直到它被解释为止。深。
定义:
声明性属性是只存在一组可以表达每个特定模块语义的可能语句的地方。
命令性属性3是双重的,其中语义在组合下是不一致的和/或可以用语句集的变体来表达。
声明性的这种定义在语义范围内具有明显的局部性 ,这意味着它要求模块化语义保持其一致的含义,无论在何处以及如何在全局范围内实例化和使用它。 因此,每个声明性模块语义应该与所有可能的其他语义本质上正交 - 而不是不可能(由于不完整性定理)用于见证一致性的全局算法或模型,这也是Robert Harper教授的“ 更多并不总是更好 ”的观点。卡内基梅隆大学计算机科学系,标准ML的设计者之一。
这些模块化声明性语义的示例包括类别理论仿函数,例如
Applicative
,名义类型,命名空间,命名字段,以及语义操作级别的wrt,然后是纯函数式编程。因此,设计良好的声明性语言可以更清楚地表达意义 ,尽管在可以表达的内容中失去了一般性,但在内在一致性中可以表达的内容却有所增加。
上述定义的一个例子是电子表格程序的单元格中的公式集合 - 当移动到不同的列和行单元格时,预期不会给出相同的含义,即单元格标识符改变。 单元标识符是预期含义的一部分,而不是多余的。 因此,每个电子表格结果对于一组公式中的单元标识符是唯一的。 在这种情况下,一致的模块化语义是使用单元标识符作为单元格公式的纯函数的输入和输出(见下文)。
超文本标记语言HTML的又名静态网页pages-语言是一种高度(但不是完全的一个例子3 (至少HTML 5之前)没有能力来表达动态行为)说明性语言。 HTML可能是最容易学习的语言。 对于动态行为,JavaScript等命令式脚本语言通常与HTML结合使用。 没有JavaScript的HTML符合声明性定义,因为每个名义类型(即标签)在语法规则内的组合下保持其一致的含义。
声明性的竞争定义是语义语句的可交换和幂等属性,即语句可以在不改变含义的情况下重新排序和复制。 例如,如果这些名称是模块化的,则可以重新排序和复制为命名字段分配值的语句,而不会改变程序的含义。 名称有时暗示订单,例如单元格标识符包括其列和行位置 - 在电子表格上移动总计更改其含义。 否则,这些属性隐式地要求语义的全局一致性。 通常不可能设计语句的语义,因此如果随机排序或重复,它们保持一致,因为顺序和重复是语义固有的。 例如,语句“Foo exists”(或构造)和“Foo不存在”(和销毁)。 如果考虑到预期语义的随机不一致性,那么人们接受这个定义对于声明性属性就足够了。 从本质上讲,这个定义是一个通用的定义,因为它试图使一致性与语义正交,即无视语义宇宙是动态无界的,并且无法在全局一致性范式中捕获这一事实。
要求较低级操作语义的(结构评估顺序)的交换和幂等属性将操作语义转换为声明性本地化模块语义,例如纯函数编程(包括递归而不是命令循环)。 然后,实现细节的操作顺序不会影响(即全局扩展)更高级语义的一致性。 例如,电子表格公式的评估顺序(理论上也是重复)无关紧要,因为在计算完所有输出之前,输出不会复制到输入,即类似于纯函数。
C,Java,C ++,C#,PHP和JavaScript不是特别声明的。 Copute的语法和Python的语法更加声明地耦合到预期的结果 ,即一致的语法语义,消除了无关的内容,因此人们可以在忘记代码后轻松理解代码。 Copute和Haskell强制执行操作语义的确定性,并鼓励“ 不要重复自己 ”(DRY),因为它们只允许纯粹的功能范式。
2即使我们可以证明程序的语义,例如使用语言Coq,这仅限于在键入时表达的语义,并且键入永远不能捕获程序的所有语义 - 甚至不能用于语言没有图灵完整,例如使用HTML + CSS,可以表达不一致的组合,因此具有未定义的语义。
3许多解释错误地声称只有命令式编程具有语法上有序的语句。 我澄清了命令式和函数式编程之间的这种混淆 。 例如,HTML语句的顺序不会降低其含义的一致性。
编辑:我在Robert Harper的博客上发表了以下评论 :
在函数式编程中......变量的变化范围是一种类型
根据人们如何将功能与命令式编程区分开来,命令式程序中的“可分配”也可能具有限制其可变性的类型。
我目前对函数式编程感兴趣的唯一非混乱定义是:a)作为第一类对象和类型的函数,b)对循环递归的偏好,和/或c)纯函数 - 即那些不影响所需语义的函数当被记忆时程序( 因此,由于操作语义的影响,例如存储器分配 ) ,在通用指称语义中不存在完全纯粹的函数编程 。
纯函数的幂等属性意味着对其变量的函数调用可以用其值替换,这通常不是命令式过程的参数的情况。 对于输入和结果类型之间的未组合状态转换,纯函数似乎是声明性的。
但是纯函数的组合并不保持任何这样的一致性,因为可以在纯函数式编程语言中模拟一个副作用(全局状态)命令性过程,例如Haskell的IOMonad,而且完全不可能阻止这样做。任何图灵完整的纯函数式编程语言。
正如我在2012年所写的那样,在你最近的博客中似乎有类似的评论共识,声明性编程试图捕捉到预期的语义从不透明的概念。 不透明语义的例子是依赖于顺序,依赖于操作语义层的高级语义的擦除(例如, 强制转换不是转换,而有效的泛型限制了更高级别的语义 ),以及对无法检查的变量值的依赖(证明)正确的)编程语言。
因此,我得出结论,只有非图灵完整语言才能声明。
因此,声明性语言的一个明确且不同的属性可以是其输出可以被证明服从一些可枚举的生成规则集。 例如,对于没有脚本化的任何特定HTML程序(忽略解释器分歧的方式的差异)(即不是图灵完成),则其输出可变性可以是可枚举的。 或者更简洁的是,HTML程序是其可变性的纯函数。 同上电子表格程序是其输入变量的纯函数。
所以在我看来,声明性语言是无界递归的对立面,即根据哥德尔的第二个不完备性定理,自指示定理无法证明。
Lesie Lamport 写了一篇童话故事,讲述了欧几里德如何通过类型和逻辑之间的一致性(Curry-Howard对应等)来解决在编程语言语境中应用于数学证明的哥德尔不完备性定理。
#4楼
这里有一些关于注意到的“类型”的好答案。
我提交了一些通常与功能编程人群相关的其他更“异国情调”的概念:
- 领域特定语言或DSL编程:创建一种新语言来处理手头的问题。
- 元编程 :当您的程序编写其他程序时。
- 进化编程 :在这种情况下,您可以构建一个不断改进自身或连续生成更好的子程序的系统。
#5楼
如今,新的焦点:我们需要旧的分类?
过去的命令/声明/功能方面很好地对泛型语言进行了分类,但是现在所有“大语言”(如Java,Python,Javascript等)都有一些选项(通常是框架 )来表达“其他焦点”比它的主要(通常的命令),并表达并行进程,声明函数,lambdas等。
因此,这个问题的一个很好的变体是“今天对框架进行分类的哪个方面很好?” ......一个重要方面是我们可以标记“编程风格” ......
专注于数据与算法的融合
一个很好的例子来解释。 你可以在维基百科上阅读关于jQuery的内容 ,
jQuery核心功能集 - DOM元素选择,遍历和操作 - 由其选择器引擎(...)启用,创建了一种新的“编程风格”,融合了算法和DOM数据结构
因此,jQuery是专注于“新编程风格”的最佳(流行)示例,不仅是面向对象,而是“ 融合 算法和数据结构 ”。 jQuery在电子表格方面有些反应,但不是“面向单元格”,是“以DOM节点为导向 ”......在此上下文中比较主要样式 :
没有融合 :在所有“大语言”中,在任何功能/声明/命令式表达中,通常是数据和算法的“无融合”,除了一些面向对象,即严格的代数结构观点的融合 。
一些融合 :所有经典的融合策略,在当今都有一些框架使用它作为范例... 数据流 , 事件驱动编程 (或旧域特定语言如awk和XSLT )...就像使用现代电子表格编程一样,它们也是反应式编程风格的例子。
Big fusion :是“jQuery风格”...... jQuery是一种专注于“ 融合算法和DOM数据结构 ”的领域特定语言。
PS:其他“查询语言”,如XQuery,SQL(PL作为命令式表达选项)也是数据算法融合示例,但它们是孤岛 ,没有与其他系统模块融合...... Spring ,当使用find()
-variants和Specification子句,是另一个很好的融合示例。
#6楼
命令式编程:告诉“机器”如何做某事,结果你想要发生的事情就会发生。
声明性编程:告诉“机器”你想要发生什么,让计算机弄清楚如何去做。
势在必行的例子
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);
}
注意:区别不在于简洁,复杂或抽象。 如上所述,区别在于如何与什么相比 。
#7楼
声明性编程是通过在输入和输出之间表达一些永恒逻辑来编程,例如,在伪代码中,以下示例将是声明性的:
def factorial(n):
if n < 2:
return 1
else:
return factorial(n-1)
output = factorial(argvec[0])
我们在这里定义一个称为“阶乘”的关系,并将输出和输入之间的关系定义为该关系。 这里应该很明显,关于任何结构化语言都允许在某种程度上进行声明性编程。 声明性编程的一个中心思想是不可变数据,如果你分配给一个变量,你只会这样做一次,然后再也不会。 其他更严格的定义意味着可能根本没有副作用,这些语言有时被称为“纯粹的声明性”。
命令式风格的相同结果将是:
a = 1
b = argvec[0]
while(b < 2):
a * b--
output = a
在这个例子中,我们在输入和输出之间没有表达永恒的静态逻辑关系,我们手动更改了内存地址,直到其中一个保持了所需的结果。 显而易见的是,所有语言都允许声明性语义在某种程度上,但并非所有语言都允许使用命令式,一些“纯粹”声明性语言允许副作用和完全变异。
声明性语言通常被称为“必须做什么”而不是“如何做”,我认为这是一个用词不当,声明性程序仍然指定了必须如何从输入到输出,但另一方面,你指定的关系必须是有效的可计算的 (重要的术语,如果你不知道它,请查阅它)。 另一种方法是非确定性编程,它实际上只是指定结果满足的条件,然后在实现之前耗尽所有试验和错误的路径直到成功。
纯粹的声明性语言包括Haskell和Pure Prolog。 从一个到另一个的滑动比例将是:Pure Prolog,Haskell,OCaml,Scheme / Lisp,Python,Javascript,C--,Perl,PHP,C ++,Pascall,C,Fortran,Assembly
#8楼
命令式编程是指任何编程风格,其中程序的结构是由描述计算机执行操作的指令构成的。
声明性编程是指任何编程风格,其中您的程序是问题或解决方案的描述 - 但没有明确说明工作将如何完成 。
函数式编程是通过评估函数的函数和函数进行编程的......作为(严格定义的)函数式编程意味着通过定义无副作用的数学函数进行编程,因此它是一种声明式编程,但它不是唯一的声明性编程 。
逻辑编程 (例如在Prolog中)是另一种形式的声明性编程。 它涉及通过判断逻辑语句是否为真(或者是否可以满足)来进行计算。 该程序通常是一系列事实和规则 - 即描述而不是一系列指令。
术语重写 (例如CASL)是声明性编程的另一种形式。 它涉及代数术语的象征性转换。 它完全不同于逻辑编程和函数式编程。
#9楼
对于这些,没有任何非模棱两可的客观定义。 我将如何定义它们:
势在必行 -重点是什么步骤,计算机应该采取而不是什么电脑会做 (如C,C ++,Java的)。
声明性 - 重点在于计算机应该做什么而不是应该如何做(例如SQL)。
功能 - 声明语言的一个子集,重点关注递归
#10楼
我认为您的分类法不正确。 命令式和声明性有两种相反的类型。 功能只是声明的一个子类型。 顺便说一句,维基百科说明了同样的事实。
#11楼
简而言之:
命令式语言指定计算机按顺序执行的一系列指令(执行此操作,然后执行此操作)。
声明性语言声明了一组关于哪些输出应该来自哪些输入的规则(例如,如果你有A,那么结果是B)。 引擎会将这些规则应用于输入,并提供输出。
函数式语言声明了一组数学/逻辑函数,用于定义输入如何转换为输出。 例如。 f(y)= y * y。 它是一种声明性语言。
#12楼
命令式和陈述式描述了两种相反的编程风格。 当务之急是传统的“循序渐进的配方”方法,而声明更多是“这就是我想要的,现在你要弄清楚如何去做”。
这两种方法贯穿整个编程 - 即使使用相同的语言和相同的程序。 一般来说,声明性方法被认为是可取的,因为它使程序员不必指定如此多的细节,同时也有更少的机会获取错误(如果你描述了你想要的结果,并且一些经过良好测试的自动过程可以从那个向后工作定义步骤然后你可能希望事情比必须手动指定每个步骤更可靠。
另一方面,一种命令式方法可以让您进行更低级别的控制 - 这是编程的“微管理方法”。 这可以让程序员利用有关问题的知识来提供更有效的答案。 因此,程序的某些部分以更具说明性的方式编写并不罕见,但速度关键部分更为必要。
正如您可能想象的那样,您用来编写程序的语言会影响您的声明性 - 一种内置“智能”的语言,用于计算结果,给出对结果的描述将允许更多的声明程序员需要首先在命令式代码中添加这种智能才能在顶部构建更具声明性的层。 因此,例如,像prolog这样的语言被认为是非常具有声明性的,因为它内置了一个搜索答案的过程。
到目前为止,你会注意到我没有提到函数式编程。 那是因为它是一个术语,其含义与其他两个词没有直接关系。 最简单的函数式编程意味着您可以使用函数。 特别是,您使用支持函数作为“第一类值”的语言 - 这意味着您不仅可以编写函数,还可以编写函数来编写函数(编写函数...),并将函数传递给功能。 简而言之 - 功能与字符串和数字一样灵活和通用。
然后,通常会一起提到功能性,命令性和声明性。 其原因是将功能编程理念“推向极端”。 从最纯粹的意义上讲,函数是来自数学的东西 - 一种“黑盒子”,它接受一些输入并始终提供相同的输出。 而且这种行为不需要存储变化的变量。 因此,如果你设计一种编程语言,其目的是实现一种非常纯粹的,数学上受影响的函数式编程思想,那么你最终会主要拒绝可以改变的价值观(在一定的,有限的,技术意义上)。
如果你这样做 - 如果你限制变量的变化 - 那么几乎是偶然的,你最终迫使程序员编写更具说明性的程序,因为命令式编程的很大一部分是描述变量如何变化,你不能再去做! 事实证明,函数式编程 - 特别是函数式编程 - 往往会提供更多的声明性代码。
总结一下,然后:
命令式和声明式是两种相反的编程风格(相同的名称用于鼓励这些风格的编程语言)
函数式编程是一种编程风格,其中函数变得非常重要,因此,改变值变得不那么重要。 指定值更改的能力有限会强制使用更具说明性的样式。
所以“函数式编程”通常被描述为“声明式”。
#13楼
势在必行: 如何实现我们的目标
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
#14楼
在撰写本文时,此页面上的最高投票答案在声明性与命令性定义上是不精确和混淆的,包括引用维基百科的答案。 一些答案以不同的方式混淆了这些术语。
另请参阅我对电子表格编程为什么是声明性的解释 ,无论公式如何改变单元格。
此外,一些答案声称函数式编程必须是声明性的子集。 在这一点上,它取决于我们将“功能”与“程序”区分开来。 让我们先处理命令式与声明性。
声明性表达的定义
可能将声明性表达式与命令式表达式区分开来的唯一属性是其子表达式的引用透明度(RT)。 所有其他属性在两种类型的表达式之间共享,或者从RT派生。
100%声明性语言(即其中每个可能的表达式都是RT)不会(在其他RT要求中)允许存储值的变异,例如HTML和大多数Haskell。
RT表达式的定义
RT通常被称为“无副作用”。 术语效果没有精确的定义,因此有些人不同意“没有副作用”与RT相同。 RT有一个精确的定义 。
由于每个子表达式在概念上都是函数调用,因此RT要求函数的实现(即被调用函数内的表达式)可能无法访问函数外部的可变状态(访问可变局部状态是允许)。 简而言之,功能(实现)应该是纯粹的 。
纯函数的定义
纯函数通常被认为具有“无副作用”。 效果一词没有准确的定义,所以有些人不同意。
纯函数具有以下属性。
- 唯一可观察的输出是返回值。
- 唯一的输出依赖是参数。
- 在生成任何输出之前完全确定参数。
请记住,RT适用于表达式(包括函数调用),纯度适用于(函数的实现)。
使用RT表达式的不纯函数的一个模糊示例是并发的,但这是因为在中断抽象层中纯度被破坏了。 你真的不需要知道这一点。 要制作RT表达式,可以调用纯函数。
RT的衍生属性
声明性编程引用的任何其他属性,例如维基百科使用的1999年引用 ,或者源自RT,或者与命令式编程共享。 从而证明我的精确定义是正确的。
注意, 外部值的不变性是RT要求的一个子集。
声明性语言没有循环控制结构,例如
for
和while
,因为由于不变性 ,循环条件永远不会改变。声明性语言不表示嵌套函数顺序(又称逻辑依赖性)以外的控制流,因为由于不变性 ,评估顺序的其他选择不会改变结果(见下文)。
声明性语言表示逻辑“步骤”(即嵌套的RT函数调用顺序),但是每个函数调用是否是更高级别的语义(即“要做什么”)不是声明性编程的要求。 与命令的区别在于, 由于不变性 (即更一般地说是RT),这些“步骤”不能依赖于可变状态,而只依赖于表达逻辑的关系顺序(即函数调用的嵌套顺序,即子表达式) )。
例如,在评估段落中的子表达式(即标记)之前,不能显示HTML段落
<p>
。 由于标签层次结构的逻辑关系(子表达式的嵌套 , 类似嵌套的函数调用 ),因此没有可变状态,只有顺序依赖性。因此,存在不变性的衍生属性(更一般地说是RT),声明性表达式仅表达组成部分(即子表达式函数参数)的逻辑关系而不表达可变状态关系。
评估订单
当任何函数调用不是RT时(即函数不是纯函数),子表达式的评估顺序的选择只能给出变化的结果,例如,在函数内访问函数外部的一些可变状态。
例如,给定一些嵌套表达式,例如f( g(a, b), h(c, d) )
,如果函数f
, g
和h
是纯的,那么对函数参数的急切和惰性求值将给出相同的结果。
然而,如果函数f
, g
和h
不是纯粹的,那么评估顺序的选择可以给出不同的结果。
注意,嵌套表达式是概念上嵌套的函数,因为表达式运算符只是伪装成一元前缀,一元后缀或二进制中缀表示法的函数调用。
切线地,如果所有标识符(例如a
, b
, c
, d
)在任何地方都是不可变的 ,则无法访问程序外部的状态(即I / O),并且没有抽象层破坏,那么函数总是纯粹的。
顺便说一句,Haskell有不同的语法, f (gab) (hcd)
。
评估订单详情
函数是从输入到输出的状态转换(不是可变的存储值)。 对于调用纯函数的RT组合,这些状态转换的执行顺序是独立的。 由于缺乏副作用以及RT函数可能被其缓存值替换的原则,每个函数调用的状态转换独立于其他函数调用。 为了纠正一个流行的误解 ,纯粹的monadic组合总是声明和RT ,尽管事实上Haskell的IO
monad 可以说是不纯的 ,因此势在必行的World
状态在程序之外(但在下面的警告意义上,侧面 - 效果是孤立的)。
急切评估意味着在调用函数之前评估函数参数,而惰性评估意味着在函数内访问参数之前(以及之后) 不会评估参数 。
定义 :函数参数在函数定义站点声明,函数参数在函数调用站点提供。 知道参数和参数之间的区别。
从概念上讲,所有表达式都是(组合)函数调用,例如,常量是没有输入的函数,一元运算符是具有一个输入的函数,二元中缀运算符是具有两个输入的函数,构造函数是函数,甚至是控制语句(例如, if
, for
, while
)可以用函数建模。 在这些命令 参数的函数(不使用嵌套函数调用为了混淆)的评估不是由语法,如宣布f( g() )
可能急切地评估g
然后f
上g
的结果,也可能评估f
只有懒惰地评估g
在f
内需要的结果。
警告,没有图灵完整语言(即允许无限递归)是完美的声明,例如懒惰评估引入记忆和时间不确定性。 但是由于评估顺序的选择而产生的这些副作用仅限于存储器消耗,执行时间,等待时间,非终止和外部滞后因此外部同步。
功能编程
因为声明性编程不能有循环,所以迭代的唯一方法是函数递归。 从这个意义上讲,函数式编程与声明性编程有关。
但函数式编程不仅限于声明式编程 。 功能组合可以与子类型形成对比 ,特别是对于表达问题 ,其中可以通过添加子类型或功能分解来实现扩展。 扩展可以是两种方法的混合 。
函数式编程通常使函数成为第一类对象,这意味着函数类型可以出现在任何其他类型的语法中。 结果是功能可以输入和操作功能,从而通过强调功能组成来提供关注点分离,即分离确定性计算的子计算之间的依赖性。
例如,对于可以应用于集合的每个元素的无限多个可能的专用操作中的每一个, 不是编写单独的函数 (并且如果函数也必须是声明的,则使用递归而不是循环),函数编程使用可重用的迭代。功能,例如map
, fold
, filter
。 这些迭代函数输入一流的专用动作函数。 这些迭代函数迭代集合并为每个元素调用输入专用操作函数。 这些操作函数更简洁,因为它们不再需要包含循环语句来迭代集合。
但是,请注意,如果函数不纯,那么它实际上是一个过程。 我们或许可以争辩说使用不纯函数的函数式编程实际上是程序式编程。 因此,如果我们同意声明性表达式是RT,那么我们可以说过程式编程不是声明式编程,因此我们可能会认为函数式编程总是RT并且必须是声明式编程的子集。
排比
这种具有一等函数的功能组合可以通过分离出独立的功能来表达并行性的深度 。
布伦特原理:工作w和深度d的计算可以在时间O(max(w / p,d))的p处理器PRAM中实现。
并发和并行性也需要声明性编程 ,即不变性和RT。
那么这个危险的假设是什么,Parallelism == Concurrency来自哪里呢? 这是带有副作用的语言的自然结果:当你的语言到处都有副作用时,那么每次你尝试做多个事情时,你基本上都会产生由每个操作的效果交错引起的非确定性。 因此,在副作用语言中,获得并行性的唯一方法是并发; 因此,我们经常看到两者混为一谈并不奇怪。
FP评估订单
请注意,评估顺序还会影响功能组合的终止和性能副作用。
渴望(CBV)和懒惰(CBN)是分类决斗[ 10 ],因为它们具有相反的评估顺序,即分别是首先评估外部函数还是内部函数。 想象一个倒置的树,然后急切地从功能树分支提示分支层次结构到顶级功能主干; 然而,懒惰评估从树干到分支提示。 渴望没有联合产品(“和”,a / k / a分类“产品”),懒惰没有析取副产品(“或”,a / k / a分类“总和”)[ 11 ]。
性能
急于
与非终止一样,渴望过于渴望联合功能组合,即组合控制结构做不必要的工作,而不是懒惰。 例如 ,当整个列表由一个终止于第一个真元素的折叠组成时,急切地并且不必要地将整个列表映射到布尔值。
这种不必要的工作是所谓的“达到”额外的log n因子的原因 ,在具有纯函数的渴望与懒惰的连续时间复杂度中。 一种解决方案是使用具有惰性构造函数的函子(例如列表)(即,渴望使用可选的惰性产品),因为急切地渴望不正确性来自内部函数。 这是因为产品是建设性的类型,即在初始定点上具有初始代数的归纳类型[ 11 ]
懒
与非终止一样,懒惰过于懒惰,具有析取的功能组成,即共同结束可能在必要时间之后发生,导致不必要的工作和迟到的非确定性,而不是急切的情况[ 10 ] [ 11 ] 。 最终的例子是状态,定时,非终止和运行时异常。 这些是必要的副作用,但即使在纯粹的声明性语言(例如Haskell)中,在命令式IO monad中存在状态(注意:并非所有monad都是命令性的!)隐含在空间分配中,而时序是相对于命令式的状态真实世界。 即使使用可选的急切副产品,使用延迟也会将“懒惰”泄漏到内部副产品中,因为懒惰的懒惰不正确性来自外部函数 (请参阅非终止部分中的示例,其中==是外部二元运算符函数)。 这是因为副产品受到终结性的限制,即在最终对象上具有最终代数的共同类型[ 11 ]。
懒惰导致延迟和空间函数的设计和调试中的不确定性,其调试可能超出了大多数程序员的能力,因为声明的函数层次结构和运行时评估顺序之间存在不一致 。 使用eager评估的惰性纯函数可能会在运行时引入以前看不见的非终止。 相反,使用lazy评估的热切纯函数可能会在运行时引入以前看不见的空间和延迟不确定性。
未结束
在编译时,由于Halting问题和Turing完整语言中的相互递归,通常不能保证函数终止。
急于
渴望但不懒惰,对于
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的比较,以及SCL中的3.6.1 CBV和CBN。
[ 11 ]声明性延续和分类二元性,Filinski,第2.2.1节产品和副产品,2.2.2终端和初始对象,2.5.2带有惰性产品的CBV,以及带有热切副产品的2.5.3 CBN。