当前位置:  开发笔记 > 编程语言 > 正文

什么是声明性编程?

如何解决《什么是声明性编程?》经验,为你挑选了12个好方法。

我一直听到这个词在几个不同的背景下被抛出.它是什么?



1> 1800 INFORMA..:

声明性编程是指您编写代码时,它描述了您想要执行的操作,而不是您想要执行的操作.它由编译器来决定如何.

声明性编程语言的示例是SQL和Prolog.


你仍然要弄清楚如何告诉计算机"你想要什么":)
@hasenj这个和其他答案没有定义[仅与命令式编程共享的属性](http://stackoverflow.com/a/8365840) - 这是**immutability**.
如果您能说出它与命令式编程(C,C ++,C#等语言)有何不同,那将是非常不错的,这样,读者将更容易发现差异。

2> Jörg W Mitta..:

其他答案已经做了很棒的工作来解释声明性编程是什么,所以我只是提供一些可能有用的例子.

背景独立

声明性程序与上下文无关.因为它们只声明最终目标是什么,而不是实现该目标的中间步骤,所以可以在不同的上下文中使用相同的程序.这很难用于命令式程序,因为它们通常依赖于上下文(例如隐藏状态).

yacc作为一个例子.它也是一个解析器生成器.编译器编译器,用于描述语言语法的外部声明性DSL,以便可以从描述中自动生成该语言的解析器.由于它的上下文独立性,你可以用这样的语法做很多不同的事情:

为该语法生成​​一个C解析器(原始用例yacc)

为该语法生成​​C++解析器

为该语法生成​​Java解析器(使用Jay)

为该语法生成​​C#解析器(使用GPPG)

为该语法生成​​Ruby解析器(使用Racc)

为该语法生成​​树形可视化(使用GraphViz)

只需对yacc源文件本身进行一些漂亮的打印,花式格式化和语法高亮显示,并将其作为语言的语法规范包含在参考手册中

还有很多 …

优化

因为你没有规定计算机采取的步骤和顺序,它可以更自由地重新安排程序,甚至可以并行执行某些任务.一个很好的例子是SQL数据库的查询规划器和查询优化器.大多数SQL数据库允许您显示它们实际执行的查询与您要求它们执行的查询.通常,这些查询看起来一无所获互相喜欢.查询规划器会考虑到您甚至没有想到的事情:例如,磁盘盘片的旋转延迟,或者完全不同的用户执行类似查询的一些完全不同的应用程序以及您所使用的表格这一事实加入并且你努力工作以避免加载已经在内存中无论如何.

这里有一个有趣的权衡:机器必须更加努力地弄清楚如何做一些比在命令式语言中做的事情,但是当它确实解决它时,它有更多的自由和更多的信息用于优化阶段.



3> William Payn..:

松散:

声明性编程倾向于: -

声明或声明性声明的集合,每个都有意义(通常在问题域中),并且可以独立地和孤立地理解.

命令式编程倾向于: -

命令序列,每个命令执行一些动作; 但在问题领域可能有意义,也可能没有意义.

因此,命令式样式有助于读者理解系统实际执行的操作的机制,但可能无法深入了解它要解决的问题.另一方面,声明式样式有助于读者理解问题领域和系统解决问题所采用的方法,但在力学问题上却缺乏信息.

真实的程序(甚至用有利于光谱末端的语言编写的程序,例如ProLog或C)往往会在不同的点上呈现不同程度的两种样式,以满足作品的不同复杂性和通信需求.一种风格并不优于另一种风格; 它们只是用于不同的目的,而且,与生活中的许多事情一样,适度是关键.



4> Niko Bellic..:

这是一个例子.

在CSS(用于设置HTML页面样式)中,如果您希望图像元素高100像素,宽100像素,您只需"声明"您想要的内容如下:

#myImageId {
height: 100px;
width: 100px;
}

您可以将CSS视为声明性的"样式表"语言.

读取和解释这个CSS 的浏览器引擎可以自由地使图像看起来很高,但它想要这么宽.不同的浏览器引擎(例如,用于IE的引擎,用于Chrome的引擎)将以不同方式实现该任务.

当然,它们的独特实现不是用声明性语言编写的,而是用程序式编写的,如Assembly,C,C++,Java,JavaScript或Python.该代码是一系列逐步执行的步骤(可能包括函数调用).它可能会执行内插像素值和在屏幕上渲染等操作.



5> Shelby Moore..:

对不起,但我必须不同意其他许多答案.我想停止对声明性编程定义的这种混乱的误解.

定义

子表达式的引用透明度(RT)是声明性编程表达式的唯一必需属性,因为它是唯一不与命令式编程共享的属性.

声明性编程的其他引用属性源自此RT.请单击上面的超链接以获取详细说明.

电子表格示例

两个答案提到电子表格编程.在电子表格编程(又名公式)不访问可变全局状态的情况下,则它是声明性编程.这是因为可变的细胞的值是该整体式输入输出的的main()(整个程序).执行每个公式后,新值不会写入单元格,因此它们在声明性程序的生命周期内不可变(执行电子表格中的所有公式).因此,相对于彼此,公式将这些可变细胞视为不可变的.允许RT函数访问不可变的全局状态(以及可变的本地状态).

因此,当程序终止(作为输出main())时,改变单元格中的值的能力不会使它们在规则的上下文中成为可变的存储值.关键的区别是在执行每个电子表格公式后不更新单元格值,因此执行公式的顺序无关紧要.在执行所有声明性公式之后更新单元格值.



6> Chris Wenham..:

声明性编程就是图片,其中命令式编程是绘制图片的指令.

如果你"告诉它它是什么",你就会以声明式的方式写作,而不是描述计算机应该走到你想要的地方的步骤.

当您使用XML来标记数据时,您正在使用声明性编程,因为您说的是"这是一个人,那是一个生日,并且有一个街道地址".

声明性和命令式编程结合起来以获得更大效果的一些示例:

Windows Presentation Foundation使用声明性XML语法来描述用户界面的外观,以及控件和底层数据结构之间的关系(绑定).

结构化配置文件使用声明性语法(就像"key = value"对一样简单)来标识数据的字符串或值的含义.

HTML使用标记来标记文本,这些标记描述了每段文本相对于整个文档的作用.


虽然XML是声明性的,但我不会说它是声明性的*编程*,因为没有与标记相关的活动语义.说某事是一个地址并没有帮助弄清楚你想用它做什么.

7> paan..:

想象一下excel页面.使用填充公式的列来计算纳税申报表.

所有逻辑都在单元格中声明完成,计算的顺序由公式本身确定,而不是在程序上确定.

这就是声明式编程的全部内容.您声明问题空间和解决方案而不是程序的流程.

Prolog是我使用的唯一声明性语言.它需要一种不同的思维方式,但是如果只是为了让你接触到除了典型的过程式编程语言之外的其他东西,这是很好的.



8> 小智..:

声明性编程是使用声明进行编程,即声明性句子.陈述句有许多属性,可以将它们与命令式句子区分开来.特别是,声明是:

可交换的(可以重新排序)

关联的(可以重新组合)

幂等(可以重复而不改变意义)

单调(声明不减去信息)

一个相关的观点是,这些都是结构性质,并且与主题正交.声明不是关于"什么与如何".我们可以像宣布"什么"一样轻松地声明(表示和约束)"如何".声明是关于结构,而不是内容.声明性编程对我们如何抽象和重构代码以及如何将其模块化为子程序产生重大影响,但对域模型却没有那么多.

通常,我们可以通过添加上下文从命令转换为声明.例如,从"向左转.(......等待......)向右转." "鲍勃将于11:01在Foo和Bar的十字路口左转.鲍勃将于11:06在Bar和Baz的交叉路口右转." 请注意,在后一种情况下,句子是幂等的和可交换的,而在前一种情况下,重新排列或重复句子会严重改变程序的含义.

关于单调,声明可以添加减去可能性的约束.但约束仍然增加信息(更确切地说,约束是信息).如果我们需要时变声明,通常用明确的时间语义对其进行建模 - 例如从"球是平的"到"球在时间T是平的".如果我们有两个相互矛盾的声明,我们就会有一个不一致的声明系统,尽管这可以通过引入约束(优先级,概率等)或利用一个不一致的逻辑来解决.



9> Kevin Berrid..:

这是围绕描述基于编程的方法有什么事应该做或可以代替描述如何它应该工作.

换句话说,你不编写由表达式组成的算法,你只需要布局你想要的东西.两个很好的例子是HTML和WPF.

这篇维基百科的文章是一个很好的概述:http://en.wikipedia.org/wiki/Declarative_programming



10> 小智..:

向计算机描述您想要的东西,而不是如何做某事。



11> Shelby Moore..:

自2011年12月我提供这个问题的答案以来,我已经完善了对声明性编程的理解.以下是我目前的理解.

我在此链接中详细介绍了我的理解(研究)的长版本,您应阅读该链接以深入理解我将在下面提供的摘要.

命令式编程是存储和读取可变状态的地方,因此程序指令的排序和/或复制可以改变程序的行为(语义)(甚至导致错误,即意外行为).

在最天真和极端的意义上(我在之前的回答中断言),声明性编程(DP)避免了所有存储的可变状态,因此程序指令的排序和/或重复不能改变程序的行为(语义) .

然而,这样的极端定义在现实世界中不会非常有用,因为几乎每个程序都涉及存储的可变状态.该表格例如符合DP的这种极端的定义,因为整个程序代码运行到完成与输入状态中的一个静态副本,存储在新的状态之前.然后,如果任何状态改变,则重复此操作.但是大多数现实世界的程序不能局限于这种状态变化的单一模型.

DP的更有用的定义是编程指令的排序和/或重复不会改变任何不透明的语义.换句话说,语义中没有隐藏的随机变化 - 程序指令顺序和/或重复的任何变化都只会导致程序行为的预期和透明变化.

下一步将讨论哪些编程模型或范例对DP有帮助,但这不是问题.



12> Shelby Moore..:

自从我写完之前的答案以来,我已经制定了一个新的声明性属性定义,引用如下.我还将命令式编程定义为双重属性.

这个定义优于我在之前的答案中提供的定义,因为它简洁而且更通用.但这可能更难以理解,因为适用于编程和生活的不完备性定理的含义对于人类来说难以包裹他们的思想.

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

声明与命令

声明性属性是奇怪的,钝的,难以捕获的技术精确定义仍然是一般的而不是模糊的,因为我们可以声明程序的含义(也就是语义)而不会产生意想不到的副作用,这是一个天真的概念.意义表达与避免意外效应之间存在着内在的张力,而这种紧张实际上源于编程和宇宙的不完备性定理.

它过于简单化,技术上不精确,并且通常含糊不清地将声明定义为" 做什么 ",将命令称为" 如何做 ".一个模糊的情况是" 什么 "是输出程序编译器的程序中的" 如何 ".

显然,使语言图灵完成的无界递归也类似于语义 - 不仅在评估的语法结构(又称操作语义)中.这在逻辑上类似于哥德尔定理的一个例子 - " 任何完整的公理系统也是不一致的 ".思考那句话的矛盾怪异!它也是一个例子,演示了语义的表达如何没有可证明的界限,因此我们无法证明2程序(以及类似的语义)也停止了哈尔丁定理.

不完备性定理源于我们宇宙的基本性质,正如热力学第二定律所述," (又称独立可能性的#)趋向于永远最大化 ".程序的编码和设计永远不会finished-它活了! -因为它试图解决一个现实世界的需要,与现实世界的语义总是在不断变化和趋势,以更多的可能性.人类永远不会停止发现新事物(包括程序中的错误;-).

为了在这个没有边缘的奇怪宇宙中精确地和技术上捕捉到上述理想的概念(思考宇宙中没有"外部"),需要一个简洁但看似不那么简单的定义,这个定义听起来不正确,直到它被解释为止.深.

定义:


声明性属性是只存在一组可以表达每个特定模块语义的可能语句的地方.

命令性属性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对应等)来解决在编程语言语境中应用于数学证明的哥德尔不完备性定理.

推荐阅读
喜生-Da
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有