我一直听到这个词在几种不同的情况下被反复提及。它是什么?
18 回答
声明式编程是当您以这样一种方式编写代码时,它描述了您想要做什么,而不是您想要如何做。由编译器决定如何操作。
声明性编程语言的示例是 SQL 和 Prolog。
其他答案已经很好地解释了声明式编程是什么,所以我将提供一些示例说明为什么它可能有用。
上下文独立
声明式程序是上下文无关的。因为他们只声明最终目标是什么,而不是达到该目标的中间步骤,所以相同的程序可以在不同的上下文中使用。这对于命令式程序很难做到,因为它们通常依赖于上下文(例如隐藏状态)。
举yacc
个例子。它是一个解析器生成器。compiler 编译器,用于描述语言语法的外部声明性 DSL,以便可以从描述中自动生成该语言的解析器。由于它的上下文独立性,你可以用这样的语法做很多不同的事情:
- 为该语法生成一个 C 解析器(的原始用例
yacc
) - 为该语法生成 C++ 解析器
- 为该语法生成一个 Java 解析器(使用 Jay)
- 为该语法生成 C# 解析器(使用 GPPG)
- 为该语法生成一个 Ruby 解析器(使用 Racc)
- 为该语法生成树可视化(使用 GraphViz)
- 只需对 yacc 源文件本身做一些漂亮的打印、花哨的格式和语法高亮,并将其作为您的语言的语法规范包含在您的参考手册中
还有很多 …
优化
因为你没有规定计算机要采取哪些步骤和顺序,它可以更自由地重新排列你的程序,甚至可以并行执行一些任务。一个很好的例子是 SQL 数据库的查询计划器和查询优化器。大多数 SQL 数据库允许您显示它们实际执行的查询与您要求它们执行的查询。通常,这些查询看起来什么都没有互相喜欢。查询计划器会考虑您甚至做梦都想不到的事情:例如磁盘盘片的旋转延迟,或者针对完全不同的用户的一些完全不同的应用程序刚刚执行了类似的查询和您所在的表这一事实加入并且你努力避免加载已经在内存中。
这里有一个有趣的权衡:与命令式语言相比,机器必须更加努力地弄清楚如何做某事,但是当它确实弄清楚时,它有更多的自由和更多的优化信息阶段。
松散地:
声明式编程倾向于:-
- 一组声明或声明性语句,每个都有意义(通常在问题域中)并且可以独立和孤立地理解。
命令式编程倾向于:-
- 命令序列,每个命令执行一些动作;但这在问题域中可能有意义,也可能没有意义。
因此,命令式风格可以帮助读者理解系统实际在做什么的机制,但可能无法深入了解它打算解决的问题。另一方面,声明式风格有助于读者理解问题域和系统为解决问题而采取的方法,但在力学问题上的信息量较少。
真正的程序(即使是用偏爱光谱末端的语言编写的程序,例如 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 语法来描述用户界面的外观,以及控件和基础数据结构之间的关系(绑定)。
结构化配置文件使用声明性语法(就像“key=value”对一样简单)来识别字符串或数据值的含义。
HTML 使用标签标记文本,这些标签描述了每段文本相对于整个文档的作用。
声明式编程是使用声明进行编程,即声明性语句。陈述句有许多区别于祈使句的特性。特别是,声明是:
- 可交换的(可以重新排序)
- 联想(可以重新组合)
- 幂等(可以重复而不改变含义)
- 单调的(声明不减去信息)
相关的一点是,这些都是结构属性并且与主题正交。声明式不是关于“什么与如何”。我们可以像声明“what”一样轻松声明(表示和约束)“how ” 。声明式是关于结构,而不是内容。声明式编程对我们如何抽象和重构代码以及如何将其模块化为子程序有重大影响,但对领域模型影响不大。
通常,我们可以通过添加上下文从命令式转换为声明式。例如来自“向左转。(……等待它……)向右转。” 到“鲍勃将在 11:01 在 Foo 和 Bar 的交叉口左转。Bob 将在 11:06 在 Bar 和 Baz 的交叉口右转。” 请注意,在后一种情况下,句子是幂等和可交换的,而在前一种情况下,重新排列或重复句子会严重改变程序的含义。
关于单调,声明可以添加减少可能性的约束。但是约束仍然增加了信息(更准确地说,约束就是信息)。如果我们需要随时间变化的声明,通常使用明确的时间语义对此进行建模——例如从“球是平的”到“球在时间 T 是平的”。如果我们有两个相互矛盾的声明,我们就会有一个不一致的声明系统,尽管这可以通过引入软约束(优先级、概率等)或利用超一致性逻辑来解决。
向计算机描述你想要什么,而不是如何做某事。
想象一个excel页面。使用填充公式的列来计算您的纳税申报表。
所有的逻辑都是在单元格中声明的,计算的顺序是由公式本身决定的,而不是程序上的。
这就是声明式编程的全部内容。您声明问题空间和解决方案,而不是程序的流程。
Prolog 是我使用的唯一声明性语言。它需要一种不同的思维方式,但如果只是让您接触典型的过程编程语言以外的东西,那么学习它是件好事。
自从 2011 年 12 月我提供了这个问题的答案以来,我已经完善了对声明式编程的理解。以下是我目前的理解。
我的理解(研究)的详细版本在此链接中进行了详细说明,您应该阅读它以深入了解我将在下面提供的摘要。
命令式编程是存储和读取可变状态的地方,因此程序指令的排序和/或重复可以改变程序的行为(语义)(甚至导致错误,即意外行为)。
在最幼稚和极端的意义上(我在之前的回答中断言),声明式编程(DP)正在避免所有存储的可变状态,因此程序指令的排序和/或重复不能改变程序的行为(语义) .
然而,这样一个极端的定义在现实世界中并不是很有用,因为几乎每个程序都涉及存储的可变状态。电子表格示例符合 DP的这种极端定义,因为在存储新状态之前,整个程序代码都使用输入状态的一个静态副本运行到完成。然后,如果任何状态发生变化,则重复此操作。但是大多数现实世界的程序不能局限于这种状态变化的单一模型。
DP 的一个更有用的定义是编程指令的排序和/或重复不会改变任何不透明的语义。换句话说,语义上不会发生隐藏的随机变化——程序指令顺序和/或重复的任何变化只会导致程序行为的预期和透明的变化。
下一步将讨论哪些编程模型或范式有助于 DP,但这不是这里的问题。
这是一种基于描述某事应该做什么或应该做什么而不是描述它应该如何工作的编程方法。
换句话说,你不需要编写由表达式组成的算法,你只是按照你想要的方式进行布局。两个很好的例子是 HTML 和 WPF。
这篇维基百科文章是一个很好的概述:http ://en.wikipedia.org/wiki/Declarative_programming
声明式编程是“使用符合开发人员心理模型而不是机器操作模型的语言进行编程的行为”。
解析结构化数据的问题很好地说明了声明式编程和命令式编程之间的区别。
命令式程序将使用相互递归的函数来使用输入并生成数据。声明性程序将表达定义数据结构的语法,以便随后对其进行解析。
这两种方法之间的区别在于,声明性程序创建了一种新语言,该语言比其宿主语言更接近于问题的心理模型。
听起来可能很奇怪,但我会将 Excel(或任何电子表格)添加到声明性系统列表中。这里给出了一个很好的例子。
我会解释为 DP 是一种表达方式
- 一个目标表达式,我们正在寻找的条件。有一个,也许还是很多?
- 一些已知的事实
- 扩展已知事实的规则
...并且有一个扣除引擎通常使用统一算法来找到目标。
据我所知,它开始被用来描述像 Prolog 这样的编程系统,因为 prolog(据说)是关于以抽象的方式声明事物。
它的意义越来越小,因为它具有上述用户给出的定义。应该清楚的是,Haskell 的声明式编程与 HTML 的声明式编程之间存在鸿沟。
声明式编程的其他几个例子:
- 用于数据绑定的 ASP.Net 标记。例如,它只是说“用这个源填充这个网格”,并将它留给系统如何发生。
- Linq 表达式
声明式编程很好,因为它可以帮助您简化代码的心智模型,并且因为它最终可能更具可扩展性。
例如,假设您有一个函数对数组或列表中的每个元素执行某些操作。传统代码如下所示:
foreach (object item in MyList)
{
DoSomething(item);
}
没什么大不了的。但是,如果您使用更具声明性的语法并将 DoSomething() 定义为 Action 会怎样?然后你可以这样说:
MyList.ForEach(DoSometing);
当然,这更简洁。但我敢肯定,除了在这里和那里保存两行代码之外,您还有更多的顾虑。以性能为例。旧的方式,处理必须按顺序进行。如果 .ForEach() 方法有办法让您发出信号,表明它可以自动并行处理处理怎么办?现在突然之间,您以一种非常安全的方式使您的代码成为多线程的,并且只更改了一行代码。而且,事实上,.Net 有一个扩展程序可以让您做到这一点。
- 如果您点击该链接,它会将您带到我朋友的博客文章。整篇文章有点长,但您可以向下滚动到标题为“问题”的标题_并在那里拿起它没问题。*
这取决于您如何提交文本的答案。总体而言,您可以从某个角度查看程序,但这取决于您查看问题的角度。我将帮助您开始该程序:Dim Bus, Car, Time, Height As Integr
同样,这取决于问题的总体情况。由于程序的原因,您可能不得不缩短它。希望这会有所帮助,如果没有,则需要反馈。谢谢你。