Answers:
声明式编程是指您以编写代码的方式描述您要执行的操作,而不是您要执行的操作。由编译器来决定如何做。
声明性编程语言的示例是SQL和Prolog。
其他答案已经很好地解释了声明式编程的含义,因此,我仅提供一些示例说明为什么这样做可能有用。
声明式程序与上下文无关。因为它们仅声明最终目标是什么,而不声明达到该目标的中间步骤,所以同一程序可以在不同的上下文中使用。使用命令式程序很难做到这一点,因为命令式程序通常取决于上下文(例如隐藏状态)。
以yacc
作为一个例子。这是一个解析器生成器。编译器编译器,一种用于描述语言语法的外部声明性DSL,以便可以从描述中自动生成该语言的解析器。由于其上下文独立性,您可以使用这种语法来做许多不同的事情:
yacc
)还有很多 …
因为您没有规定计算机要采取的步骤和顺序,它可以更加自由地重新安排程序,甚至可以并行执行某些任务。一个很好的例子是用于SQL数据库的查询计划程序和查询优化器。大多数SQL数据库允许您显示其实际执行的查询与您要求它们执行的查询。通常,这些查询看起来一无所有互相喜欢。查询计划程序考虑了您甚至梦dream以求的事情:例如磁盘磁盘的旋转等待时间,或者某个完全不同的用户的某些完全不同的应用程序刚刚执行了类似的查询,而您所使用的表与一起努力,以致您努力工作以免加载已在内存中。
有一个有趣的权衡这里:该机拥有更加努力地工作,以找出如何做一些事情会比在命令式语言,但是当它确实算起来,它对于优化更多的自由和更多信息阶段。
松散地:
声明式编程倾向于:
命令式编程倾向于:
结果,命令式样式可以帮助读者理解系统实际运行的机制,但是可能无法洞悉系统打算解决的问题。另一方面,声明式的样式可以帮助读者理解问题领域和系统对解决问题所采取的方法,但是对机械问题的了解较少。
真实的程序(甚至是那些使用最适合最终用户的语言编写的程序,例如ProLog或C)往往会在不同点上以不同程度呈现两种风格,以满足作品的不同复杂性和沟通需求。一种风格不优于另一种。它们只是为了不同的目的,就像生活中的许多事情一样,节制是关键。
这是一个例子。
在CSS(用于设置HTML页面样式)中,如果您希望图像元素的高度为100像素,而宽度为100像素,则只需“声明”即可,如下所示:
#myImageId {
height: 100px;
width: 100px;
}
您可以将CSS视为声明性的“样式表”语言。
读取和解释此CSS 的浏览器引擎可以自由使图像显得如此高和如此宽。不同的浏览器引擎(例如IE引擎,Chrome引擎)将以不同方式实现此任务。
当然,它们的独特实现不是用声明性语言编写的,而是以汇编,C,C ++,Java,JavaScript或Python之类的过程性语言编写的。该代码是一堆要逐步执行的步骤(可能包括函数调用)。它可能会做一些事情,例如插值像素值并在屏幕上渲染。
很抱歉,但是我必须不同意其他许多答案。我想停止对声明式编程的定义的这种误解。
定义
子表达式的引用透明性(RT)是声明式编程表达式的唯一必需属性,因为它是不与命令式编程共享的唯一属性。
声明式编程的其他引用属性均来自此RT。请单击上面的超链接以获取详细说明。
电子表格示例
有两个答案提到了电子表格编程。如果电子表格编程(也称为公式)无法访问可变的全局状态,则它是声明性程序设计。这是因为可变的细胞的值是该整体式输入和输出的的main()
(整个程序)。在执行每个公式之后,新值不会写入单元格中,因此在声明性程序的生命周期(电子表格中所有公式的执行)中,它们是不可变的。因此,相对于彼此,公式将这些可变单元视为不可变的。允许RT函数访问不可变的全局状态(以及可变的本地状态)。
因此,当程序终止时(作为的输出main()
),对单元格中的值进行突变的能力不会使它们在规则的上下文中可变。关键区别在于执行每个电子表格公式后不会更新单元格值,因此执行公式的顺序无关紧要。在执行所有说明性公式后,将更新单元格值。
声明式编程是图片,其中命令式编程是绘制该图片的指令。
如果要“说明它是什么”,则是以声明方式进行的,而不是描述计算机到达所需位置所应采取的步骤。
当您使用XML标记数据时,您使用的是声明式编程,因为您在说“这是一个人,那是生日,在那儿有一条街道地址”。
将声明式和命令式编程结合在一起以产生更大效果的一些示例:
Windows Presentation Foundation使用声明性XML语法来描述用户界面的外观以及控件与基础数据结构之间的关系(绑定)。
结构化配置文件使用声明性语法(如“键=值”对那样简单)来标识数据的字符串或值的含义。
HTML使用标记来标记文本,标记描述每个文本相对于整个文档所起的作用。
声明式编程是使用声明(即声明性语句)进行编程。陈述性语句具有许多使它们与命令性语句区分开的属性。特别是,声明是:
一个相关的观点是,这些都是结构特性,并且与主题正交。声明式不是关于“什么与如何”。我们可以像声明“ what”一样容易地声明(表示和约束)“ how ”。声明式的是结构,而不是内容。声明性编程对我们如何抽象和重构代码以及如何将其模块化为子程序具有重大影响,但对域模型影响不大。
通常,我们可以通过添加上下文从命令式转换为声明式。例如,从“左转。(...等待...)右转。” 到“鲍勃将在11:01在Foo和Bar的交点处左转。鲍勃将在11:06在Bar和Baz的交点处右转。” 请注意,在后一种情况下,句子是等幂和可交换的,而在前一种情况下,重新排列或重复句子将严重改变程序的含义。
关于单调性,声明可以添加约束,从而减少可能性。但是约束仍然会添加信息(更确切地说,约束就是信息)。如果需要时变声明,通常使用显式的时间语义对其进行建模-例如,从“球平坦”到“时间T处平坦”。如果我们有两个相互矛盾的声明,那么我们将有一个不一致的声明系统,尽管可以通过引入软约束(优先级,概率等)或利用超一致逻辑来解决。
自2011年12月提供该问题的答案以来,我对声明式编程有了更深入的了解。这是我目前的理解。
我的理解(研究)的详细版本在此链接上进行了详细介绍,您应该阅读该链接以深入理解我将在下面提供的摘要。
命令式编程是存储和读取可变状态的地方,因此,程序指令的顺序和/或重复可以改变程序的行为(语义)(甚至导致错误,即意想不到的行为)。
在最幼稚和极端的意义上(我在先前的回答中断言),声明式编程(DP)避免所有存储的可变状态,因此,程序指令的顺序和/或重复不能更改的行为(语义) 。
但是,这样的极端定义在现实世界中并不是很有用,因为几乎每个程序都涉及存储的可变状态。该电子表格例子符合DP的这种极端的定义,因为整个程序代码运行到完成与输入状态中的一个静态副本,存储在新的状态之前。然后,如果任何状态发生更改,则重复此操作。但是大多数现实世界的程序不能仅限于这种状态变化的整体模型。
DP的一个更有用的定义是,编程指令的顺序和/或重复不会改变任何不透明的语义。换句话说,在语义上没有隐藏的随机变化-程序指令顺序和/或重复的任何变化仅导致程序行为的预期和透明变化。
下一步将是讨论哪些编程模型或范例有助于DP,但这不是这里的问题。
Functional programming
如今,这是一个时髦的词,基本上是声明性编程的子集。当C#语言本身本质上是必不可少的时,C#语言中的LINQ是功能编程的一个元素。因此,按照该定义,C#成为一种混合动力。
这是一种基于描述的编程方法 什么事应该做或可以代替描述如何它应该工作。
换句话说,您不会编写由表达式构成的算法,而只是布局想要的事物。HTML和WPF是两个很好的例子。
这篇Wikipedia文章是一个很好的概述:http : //en.wikipedia.org/wiki/Declarative_programming
自从我写了先前的答案以来,我为声明性属性制定了新的定义,下面对此进行了引用。我也将命令式编程定义为对偶属性。
这个定义优于我在先前的回答中提供的定义,因为它简洁明了,而且更笼统。但这可能更难以理解,因为不完整定理的含义适用于编程和一般的生活,这对于人类来说很难全神贯注。
引用的定义定义讨论了纯函数式编程在声明式编程中的作用。
声明式与命令式
声明性属性很奇怪,很钝,而且很难在技术上精确的定义中捕获,该定义仍然是通用且不明确的,因为这是一个天真的想法,我们可以声明程序的含义(即语义)而不会引起意外的副作用。在意义的表达与避免意想不到的效果之间存在着内在的张力,而这种张力实际上源于编程和宇宙的不完备性定理。
将声明式定义为“ 做什么 ”和将命令式定义为“ 如何做 ”是过于简单化,技术上不精确且常常含糊不清。在输出程序的程序(编译器)中,“ 什么 ”是“ 如何 ”是一个模棱两可的情况。
显然,使图灵语言完整的无限制递归在语义上也类似,不仅在评估的句法结构(即操作语义)中。从逻辑上讲,这是一个类似于哥德尔定理的例子-“ 任何完整的公理系统也都不一致 ”。思考那句话的矛盾怪异!这也是一个示例,说明了语义表达是如何没有可证明的界限的,因此我们不能证明2(或类似的语义)程序也就是停止定理而停止。
不完全性定理源于我们宇宙的基本本质,正如热力学第二定律所述,“ 熵(也就是独立可能性的数量)正趋于永远最大化 ”。程序的编码和设计从未完成-它还活着!-因为它试图满足现实世界的需求,并且现实世界的语义总是在变化并且趋向于更多可能性。人类永远不会停止发现新事物(包括程序错误;-)。
为了在这个没有边缘的怪异宇宙中准确而技术地捕获上述期望的概念(思考!我们的宇宙没有“外面”),需要一个简短但看似不是简单的定义,在解释之前这听起来是不正确的深。
定义:
声明性属性是仅存在一组可能表示每个特定模块化语义的语句的地方。
命令属性3是双重的,其中语义在组成上是不一致的和/或可以用语句集的变体来表达。
声明性的这种定义在语义范围上是局部的,这意味着它要求模块化语义必须保持其一致的含义,而不管它在全局范围内的位置和方式如何被实例化和采用。因此,每个声明性模块化语义都应与所有其他可能语义上固有正交,而不是不可能的(由于不完全性定理)全局算法或模型,以证明一致性,这也是Robert Harper教授“ 更多并非总是更好 ” 的要点。卡耐基梅隆大学计算机科学专业的教授,是标准ML的设计师之一。
这些模块化声明性语义的实例包括类理论函子例如所述
Applicative
,标称打字,命名空间,命名字段,和WRT到语义的操作水平,那么纯功能的编程。因此,经过精心设计的声明性语言可以更清楚地表达含义,尽管可以表达的内容失去了一般性,但是可以通过固有的一致性获得表达。
前述定义的一个示例是电子表格程序的单元格中的一组公式-当移至不同的列和行单元格(即,单元格标识符已更改)时,它们预期不会具有相同的含义。单元标识符是预期含义的一部分,并且并非多余。因此,每个电子表格结果对于一组公式中的单元格标识符都是唯一的。在这种情况下,一致的模块化语义是使用单元格标识符作为单元格公式的纯函数的输入和输出(请参见下文)。
超文本标记语言(又称HTML)(静态网页的语言)是一种高度(但不是完全3)声明性语言的示例,该语言(至少在HTML 5之前)没有表达动态行为的能力。HTML也许是最容易学习的语言。对于动态行为,通常将命令性脚本语言(如JavaScript)与HTML结合使用。没有JavaScript的HTML符合声明性定义,因为每种名义类型(即标签)在语法规则内的组成下都保持其一致的含义。
声明性的竞争定义是语义语句的可交换和幂等属性,即,可以在不更改含义的情况下对语句进行重新排序和复制。例如,如果将名称分配给命名字段的语句是模块化的,则可以对其进行重新排序和重复,而无需更改程序的含义。名称有时暗示着一个顺序,例如单元格标识符包括它们的列和行位置-在电子表格上移动总计将更改其含义。否则,这些属性隐式要求全局语义的一致性。设计语句的语义通常是不可能的,因此如果随机排序或重复,则它们将保持一致,因为顺序和重复是语义固有的。例如,语句“ Foo存在”(或构造)和“ Foo不存在”(和破坏)。如果人们认为预期语义的地方随机性不一致,那么对于声明性来说,这一定义就足够普遍了。本质上,此定义作为通用定义是虚无的,因为它试图使一致性与语义正交,即无视语义世界是动态无界的,并且不能在全局一致性范例中捕获的事实。
需要低级操作语义(的结构评估顺序)的交换和幂等属性,将操作语义转换为声明性的局部模块化语义,例如纯函数式编程(包括递归而不是命令式循环)。这样,实现细节的操作顺序就不会影响(即,在全球范围内传播)高层语义的一致性。例如,电子表格公式的求值顺序(理论上也包括重复)并不重要,因为只有在计算完所有输出之后(即类似于纯函数),才将输出复制到输入中。
C,Java,C ++,C#,PHP和JavaScript并不是特别声明性的。Copute的语法和Python的语法在声明上与预期的结果相关联,即一致的语法语义消除了多余的内容,因此人们在忘记代码后可以很容易地理解它们。Copute和Haskell强制执行操作语义的确定性,并鼓励“ 不要重复自己 ”(DRY),因为它们只允许使用纯功能范式。
2即使我们可以证明程序的语义(例如使用Coq语言),也仅限于用打字表达的语义,而打字永远不能捕获程序的所有语义,即使是不是图灵完整的,例如使用HTML + CSS可以表达不一致的组合,从而具有不确定的语义。
3许多解释错误地声称仅命令式编程具有语法顺序的语句。我澄清了命令式和函数式编程之间的这种混淆。例如,HTML语句的顺序不会降低其含义的一致性。
编辑:我在罗伯特·哈珀的博客上发表了以下评论:
在函数式编程中...变量的变化范围是一种类型
根据一个人如何将功能性与命令式编程区分开来,命令式程序中的“可分配值”也可能具有限制其可变性的类型。
我目前对函数式编程唯一了解的定义是a)作为一流对象和类型的函数,b)优先于循环的递归,和/或c)纯函数,即不影响所需语义的函数记住时的程序代码(因此,由于操作语义的影响(例如内存分配),在通用的指称语义中不存在完美的纯函数式编程)。
纯函数的幂等特性意味着可以用其值替换对变量的函数调用,而命令式命令的参数通常不是这种情况。对于输入类型和结果类型之间未组合的状态转换,纯函数似乎是声明性的。
但是纯函数的组成并不能保持任何这种一致性,因为可以用纯函数编程语言(例如Haskell的IOMonad)对副作用(全局状态)命令过程进行建模,而且完全不可能防止这种情况的发生。任何图灵完整的纯函数式编程语言。
正如我在2012年所写,似乎在您最近的博客中对评论的共识类似,声明式编程是一种尝试,旨在捕捉预期的语义永远都不透明的观念。不透明语义的示例有:依赖顺序,在操作语义层依赖于高层语义的擦除(例如,强制转换不是转换,而统一的泛型限制了高层语义),以及依赖于无法检查的变量值(已证明)正确)。
因此,我得出的结论是,只有非图灵完整语言才可以声明。
因此,声明性语言的一个明确而独特的属性可能是证明其输出可以服从一些可计数的生成规则集。例如,对于未编写脚本(即图灵不完整)的任何特定HTML程序(忽略解释器分歧的方式的差异),其输出可变性就可以枚举。或更简洁地说,HTML程序是其可变性的纯函数。同样,电子表格程序是其输入变量的纯函数。
因此在我看来,声明性语言是无界递归的对立面 ,即,根据哥德尔的第二不完全性定理,自指定理无法得到证明。
Lesie Lamport 写了一个童话,讲述了Euclid可能如何围绕类型和逻辑(Curry-Howard对应关系等)的一致性,围绕哥德尔在编程语言上下文中应用于数学证明的不完全性定理进行工作。
据我所知,它开始被用来描述像Prolog这样的编程系统,因为prolog(据说)是关于以抽象方式声明事物。
它的含义越来越小,因为它具有上述用户给出的定义。应该清楚的是,Haskell的声明式编程与HTML的声明式编程之间存在鸿沟。
声明式编程的其他两个示例:
声明式编程很好,因为它可以帮助简化您的思维模型代码的 *,并且最终可能更具可扩展性。
例如,假设您有一个函数会对数组或列表中的每个元素执行某些操作。传统代码如下所示:
foreach (object item in MyList)
{
DoSomething(item);
}
没什么大不了的。但是,如果您使用更具声明性的语法,而是将DoSomething()定义为Action,该怎么办?然后您可以这样说:
MyList.ForEach(DoSometing);
当然,这更加简洁。但是我敢肯定,除了在这里和那里保存两行代码外,您还有更多的担忧。性能,例如。旧方法必须按顺序进行处理。如果.ForEach()方法可以让您发信号通知它可以自动并行处理,该怎么办?现在突然之间,您以一种非常安全的方式使代码成为多线程代码,并且只更改了一行代码。而且,实际上,.Net 有一个扩展,可以让您做到这一点。