什么是宣告式编程?

我一直听到这个词在几个不同的语境中出现,它是什么?

130274 次浏览

宣告式编程就是当你写代码的时候,它描述的是你想要做什么,而不是你想要怎么做。由编译器来决定如何。

宣告式编程语言的例子有 SQL 和 Prolog。

这是一种基于描述 什么的编程方法,有些事情应该做或者应该是,而不是描述 怎么做,它应该工作。

换句话说,你不需要编写由表达式组成的算法,你只需要按照你想要的方式进行布局。HTML 和 WPF 是两个很好的例子。

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

宣告式编程就是画,命令式编程就是画画的说明。

如果你是在“告诉它它是什么”,而不是描述计算机应该采取什么步骤来到达你想要的地方,那么你就是在用陈述式的方式写作。

当您使用 XML 标记数据时,使用宣告式编程是因为您在说“这是一个人,那是一个生日,那边是一个街道地址”。

声明性和命令式编程结合起来产生更大效果的一些例子:

  • WindowsPresentationFoundation 使用声明性 XML 语法来描述用户界面的外观,以及控件和基础数据结构之间的关系(绑定)。

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

  • HTML 用标记标记文本,这些标记描述每段文本相对于整个文档的角色。

还有几个宣告式编程的例子:

  • 用于数据绑定的 ASP.Net 标记。例如,它只是说“用这个源填充这个网格”,然后让系统来解决这个问题。
  • Linq 表达式

宣告式编程是很好的,因为它可以帮助代码的 简化你的思维模式 * ,并且因为它最终可能更具可伸缩性。

例如,假设有一个函数对数组或列表中的每个元素执行某些操作。传统代码应该是这样的:

foreach (object item in MyList)
{
DoSomething(item);
}

没什么大不了的。但是,如果使用更具声明性的语法并将 DoSomething ()定义为一个 Action,会怎么样呢?那你可以这么说:

MyList.ForEach(DoSometing);

当然,这样更简洁。但我相信您有更多的顾虑,而不仅仅是到处保存两行代码。比如性能。按照旧的方法,处理过程必须按顺序进行。如果。ForEach ()方法有一种方法可以让您发出信号,表明它可以自动并行处理?现在,您突然以一种非常安全的方式使代码成为多线程的,并且只更改了一行代码。事实上,还有一个 分机。让你做到这一点的网络。

  • “ The Problem”_ and pick it up there no Problem. *

向计算机描述你想要什么,而不是如何做某事。

据我所知,它开始被用来描述像 Prolog 这样的编程系统,因为 Prolog (据说)是用一种抽象的方式来声明事物的。

它的意义越来越小,因为它有上面用户给出的定义。很明显,哈斯克尔的宣告式编程和 HTML 的宣告式编程之间存在着巨大的鸿沟。

设想一个 Excel 页面,其中的列填充了计算纳税申报表的公式。

所有的逻辑都是在单元格中声明的,计算的顺序是由公式本身而不是程序来决定的。

这就是宣告式编程的意义所在。您声明的是问题空间和解决方案,而不是程序的流程。

Prolog 是我使用过的唯一一种声明性语言。这需要一种不同的思维方式,但如果只是为了让你接触到一些不同于典型的程序编程语言的东西,那么学习是有好处的。

其他的答案已经很好地解释了什么是宣告式编程,所以我将举出一些例子来说明为什么这可能是有用的。

上下文独立性

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

yacc为例。是个编译器编译程式又名。编译器编译程式是描述语言语法的外部声明性 DSL,因此可以根据描述自动生成该语言的解析器。由于它的上下文独立性,您可以使用这种语法做许多不同的事情:

  • 为该语法生成一个 C 语法分析器(yacc的原始用例)
  • 为该语法生成一个 C + + 解析器
  • 为该语法生成一个 Java 解析器(使用 Jay)
  • 为该语法生成一个 C # 解析器(使用 GPPG)
  • 为该语法生成一个 Ruby 解析器(使用 Racc)
  • 为该语法生成树形可视化(使用 GraphViz)
  • 只需对 yacc 源文件本身进行一些漂亮的打印、花哨的格式化和语法突显化,并将其作为语言的语法规范包含在参考手册中

还有更多 & 救命;

优化

因为你不规定计算机采取什么步骤,以什么顺序,它可以更自由地重新安排您的程序,甚至可能并行执行一些任务。SQL 数据库的查询规划器和查询优化器就是一个很好的例子。大多数 SQL 数据库允许您显示它们正在执行的查询,而不是 问道要执行的查询。通常,这些查询看起来像 没什么。查询计划程序会考虑一些你做梦都想不到的事情: 比如磁盘盘片的旋转延迟,或者一些完全不同的应用程序为一个完全不同的用户执行了一个类似的查询,而你正在加入的表以及你努力避免加载的表已经在内存中了。

这里有一个有趣的权衡: 机器必须比命令式语言更努力地计算出 怎么做来做某事,但是当它计算出 是的时,它有更多的自由和更多的优化阶段的信息。

我会解释为 DP 是一种表达方式

  • 一个 目标表达式目标表达式,我们正在寻找的条件。有一个,也许还是许多?
  • 一些已知的事实
  • 扩展已知事实的规则

有一个扣除引擎通常与 统一算法一起工作来找到目标。

这听起来可能有点奇怪,但我会将 Excel (或任何电子表格)添加到声明性系统列表中。给出了一个很好的例子 给你

我很抱歉,但我必须不同意其他许多答案。我想停止这种对宣告式编程定义的混乱误解。

定义

因为它是唯一不与命令式编程共享的属性。

其他被引用的宣告式编程属性,来源于此 RT。请点击上面的超链接以获得详细说明。

电子表格示例

有两个答案提到了电子表格编程。如果电子表格编程(又名公式)不能访问可变的 全球性的状态,那么它就是宣告式编程。这是因为可变的单元格值是 main()(整个程序)的整体 输入输出。在执行每个公式后,新值不会写入单元格,因此在声明性程序的生命周期内(执行电子表格中的所有公式) ,它们是不可变的。因此,相对于彼此,公式将这些可变单元视为不可变的。RT 函数允许访问 永恒不变全局状态(以及 Mutable < em > local state 可变 < em > 本地 状态)。

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

自从2011年12月我为这个问题提供了 abc0以来,我已经完善了我对宣告式编程的理解。以下是我目前的理解。

我的理解(研究)的长版本详细在 这个链接,你应该阅读,以获得一个深刻的理解摘要我将提供以下。

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

在最天真和极端的意义上(我在前面的答案中断言) ,宣告式编程(DP)避免了所有存储的可变状态,因此程序指令的排序和/或重复可以 没有改变程序的行为(语义)。

然而,这样一个极端的定义在现实世界中并不十分有用,因为几乎每个程序都涉及存储的可变状态。电子表格示例符合 DP 的这种极端定义,因为在存储新状态之前,整个程序代码都是用输入状态的一个静态副本运行完成的。然后,如果任何状态被更改,这将重复执行。但是,大多数现实世界的程序不能局限于这样一个单一的状态变化模型。

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

下一步将讨论哪些编程模型或范例有助于 DP,但这不是问题所在。

宣告式编程是“使用符合开发者思维模式而非机器操作模式的语言进行编程的行为”。

陈述性和命令式编程之间的区别很明显 解析结构化数据的问题说明了这一点 命令式程序会使用相互递归的函数来消耗输入 一个声明性程序可以表达一种定义 数据的结构,以便能够进行解析 这两种方法的区别在于声明性程序 创造了一种新的语言,这种语言更接近于 问题比它的主机语言更严重

宽泛地说:

宣告式编程倾向于:-

  • 一组声明或声明性语句,每个语句都有意义(通常在问题域中) ,可以独立和隔离地理解。

命令式编程倾向于:-

  • 命令序列,每个命令执行一些操作; 但是在问题域中可能有也可能没有意义。

因此,命令式风格有助于读者理解系统实际上在做什么的机制,但是可能对它要解决的问题没有多少洞察力。另一方面,陈述式风格有助于读者理解问题领域和系统解决问题的方法,但在机制方面的信息较少。

真正的程序(甚至是那些用语言编写的程序,比如 ProLog 或者 C)倾向于在不同的点上以不同的程度呈现两种风格,以满足不同的复杂性和交流需求。一种风格并不优于另一种风格; 它们只是服务于不同的目的,而且,就像生活中的许多事情一样,适度是关键。

宣告式编程是用声明来编程的,也就是说,声明性的句子。陈述句有许多区别于祈使句的属性。具体而言,声明如下:

  • 可交换的(可重新排序的)
  • 联合的(可以重新组合)
  • 幂等(可以重复而不改变意思)
  • 单调(声明不减信息)

一个相关的观点是,这些都是结构性质,是正交的主题。声明式不是关于 “什么和怎么做”的。我们可以声明(表示和约束)一个 “如何”,就像我们声明一个 “什么”一样容易。陈述性是关于结构,而不是内容。宣告式编程对我们如何抽象和重构代码,以及如何将其模块化为子程序有重大影响,但对域模型影响不大。

通常,我们可以通过添加上下文将命令式转换为声明式。例如“向左转。右转“ Bob 会在11点01分,在 Foo 和 Bar 的十字路口左转”。Bob 会在11:06在 Bar 和 Baz 的十字路口右转注意,在后一种情况下,句子是幂等和可交换的,而在前一种情况下,重新排列或重复句子将严重改变程序的意义。

关于 单调,声明可以添加减去 可能性约束。但是约束仍然会添加信息(更准确地说,约束就是信息)。如果我们需要时变声明,典型的做法是使用显式的时间语义对其进行建模——例如,从“球是平的”到“球在时间 T 处是平的”。如果我们有两个相互矛盾的声明,我们就有一个不一致的声明系统,尽管这可以通过引入 软弱约束(优先级、概率等)或利用一个次协调逻辑来解决。

自从我写了我的前面的答案,我已经制定了一个 新定义的声明性财产,引述如下。我还将命令式编程定义为双重属性。

这个定义比我之前的回答要好,因为它更简洁,也更一般。但是它可能更难理解,因为适用于编程和一般生活的不完备性定理的含义对人类来说很难理解。

引用的定义解释讨论了 纯洁函数式编程在宣告式编程中的作用。

陈述式与命令式

声明性属性是奇怪的,迟钝的,并且很难在一个技术上精确的定义中捕获,这个定义仍然是通用的,并且没有歧义,因为我们可以声明程序的意义(也就是语义)而不会产生意想不到的副作用,这是一个天真的概念。在表达意义和避免意想不到的效果之间存在着一种内在的张力,这种张力实际上源于编程的 不完全性定理不完全性定理和我们的宇宙。

将声明式定义为 “该怎么办”和命令式定义为 怎么做过于简单化,在技术上不精确,而且常常含糊不清。一个模棱两可的例子是“ 什么”是输出程序的程序中的“ 怎么做”ー编译器。

显然,使语言图灵完成的无限递归在语义上也是类似的,不仅仅是在评价的句法结构上(也称为操作语义学)。这在逻辑上是一个类似哥德尔定理ーー“ 任何完整的公理系统也是不一致的”的例子。仔细想想这句话自相矛盾的奇怪之处吧!这个例子也证明了语义的表达式没有一个可证明的界限,因此我们不能证明 2程序(和类似的语义)停止又称停止定理。

不完备性定理来源于我们宇宙的基本性质,正如热力学第二定律中所说的“ (又名独立可能性的 #) 永远趋于最大化”。程序的编码和设计永远不会结束ーー它是活的! ーー因为它试图满足现实世界的需要,而现实世界的语义总是在变化,并趋向于更多的可能性。人类从不停止发现新事物(包括程序中的错误)。

在这个没有边界的怪异宇宙中,准确地、技术上地捕捉上述所期望的概念(想想吧!我们的宇宙没有“外部”) ,需要一个简洁但看似不简单的定义,这听起来是不正确的,直到它被深入解释。

定义:


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

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


声明式的这个定义在语义范围中是独特的 本地,这意味着它要求模块化语义保持其一致的意义,而不管它在 全球性的范围中被实例化和使用的位置和方式如何。因此,每一个声明性模块语义都应该与其他所有可能的语义在本质上是正交的,而不是一个不可能的(由于不完全性定理)证明一致性的 全球性的算法或模型,这也是计算机科学教授、标准机器学习的设计者之一罗伯特•哈珀(Robert Harper)的“ 更多并不总是更好”的卡内基梅隆大学。

这些模块化声明语义的例子包括范畴理论函数,例如 Applicative、名义类型、名称空间、命名字段和 w.r.t,它们可以用于语义的操作级,然后是纯函数式编程。

因此,设计良好的声明性语言可以使用 更清楚地表达意思,尽管在可以表达的内容方面有一些通用性的损失,但是在可以以内在一致性表达的内容方面有所收获。

上述定义的一个例子是电子表格程序单元格中的一组公式ーー当移动到不同的列和行单元格(即单元格标识符更改)时,这些公式的含义不会相同。单元格标识符是预期意义的一部分,而不是多余的。因此,对于一组公式中的单元格标识符,每个电子表格结果都是唯一的 w.r.t。在这种情况下,一致的模块语义是使用单元格标识符作为单元格公式的 纯洁函数的输入和输出(见下文)。

超文本标记语言(HyperText Markup Language,简称 HTML)ーー静态网页的语言ーー是一种高度(但不完美的 3)声明性语言的例子,这种语言(至少在 HTML 5之前)没有表达动态行为的能力。HTML 也许是最容易学的语言。对于动态行为,命令式脚本语言(如 JavaScript)通常与 HTML 结合使用。没有 JavaScript 的 HTML 符合声明性定义,因为每个名义类型(即标记)在语法规则的组合下保持其一致的含义。

声明性的一个相互竞争的定义是语义语句的 交换无能为力属性,也就是说,语句可以重新排序和复制而不改变其含义。例如,将值赋给命名字段的语句可以重新排序和复制,而不需要更改程序的含义,如果这些名称是模块化的 w.r.t. 以任何隐含的顺序。名称有时意味着顺序,例如单元格标识符包括它们的列和行位置ーー在电子表格中移动总数会改变其含义。否则,这些属性隐式地需要 全球性的语义的一致性。设计语句的语义使它们在随机排序或重复时保持一致通常是不可能的,因为顺序和重复是语义固有的。例如,语句“ Foo 存在”(或构造)和“ Foo 不存在”(和销毁)。如果认为随机的不一致性是预期语义的地方性特征,那么可以接受这个定义作为声明性属性的通用性。从本质上说,这个定义作为一个广义的定义是空洞的,因为它试图使一致性与语义正交,也就是说,它违背了这样一个事实,即语义的宇宙是动态无界的,不能在 全球性的一致性范式中捕获。

要求交换和幂等属性的(结构评估顺序的)低级操作语义学转换操作语义学到一个声明性的 局部的模块语义,例如 纯洁函数编程(包括递归而不是命令循环)。然后,实现细节的操作顺序不会影响(即将 全球范围内扩展到)高级语义的一致性。例如,电子表格公式的计算顺序(理论上也是重复的)并不重要,因为直到所有输出都计算完毕之后,输出才会复制到输入,即类似于纯函数。

C、 Java、 C + + 、 C # 、 PHP 和 JavaScript 不是特别具有声明性。 Copute 的语法和 Python 的语法更加声明性地与 预期的结果 ,即一致的句法语义,消除无关的,以便人们可以随时 理解代码后,他们已经忘记了它。 Copute 和 Haskell 强制 决定论的操作语义学和鼓励“不要重复” 你自己(DRY) ,因为他们只允许纯粹的功能范式

即使我们可以证明一个程序的语义,例如使用 Coq 语言,这也仅限于在 打字中表达的语义,并且类型化永远不能捕获一个程序的所有语义ーー即使对于不是图灵完成的语言,例如使用 HTML + CSS,也可以表达不一致的组合,因此具有未定义的语义。

许多错误的解释声称只有命令式编程有句法顺序的陈述。我澄清了这个 命令式编程和函数式编程之间的混淆。例如,HTML 语句的顺序不会降低其含义的一致性。


编辑: 我在 Robert Harper 的博客上发布了 以下评论:

在函数式编程中... 变量的变化范围是一种类型

这取决于人们如何区分函数式和祈使式 在命令式程序中,你的“可分配”也可能有 对其可变性进行限定的类型 目前我唯一欣赏的功能性定义 编程是 a)作为第一类对象和类型的函数,b) 优先选择递归而非循环,以及/或 c)纯函数ーー即。 那些不影响所需语义的函数 当制表程序(< sub > < sup > 因此完全纯粹的功能 编程不存在于通用指称语义中 由于操作语义学的影响,例如记忆力 分配 纯函数的幂等性指的是调用 它的变量可以被它的值所代替,这是不普遍的 命令式过程的参数的情况。纯函数 似乎是对未组合状态转换的声明性 WWRT 在输入类型和结果类型之间 但是纯函数的组合并不保持任何这样的特性 一致性,因为有可能建模一个副作用(全局 状态)在纯函数式编程语言中的命令式过程, 例如 Haskell 的 IOMonad,而且它完全不可能 阻止在任何图灵完全纯函数式编程中这样做 语言 < p > 正如我 写的在2012年,似乎有类似的共识 在美国广播公司宣告式编程的评论中,这是一个 试图捕捉这样一个概念,即预期的语义永远不会 不透明语义的例子是依赖于顺序, 在操作时对高级语义擦除的依赖 语义层(例如: < a href = “ https://stackoverflow.com/questions/3166840/what-is-the-different-between-cast-and-exchange/# answer-8464968”)转换不是转换,也不是具体化的泛型 限制高级语义 )和对变量值的依赖 编程语言无法检查(证明是正确的) 因此,我得出结论,只有非图灵完整语言才可以 陈述性的 因此,陈述性语言的一个明确而独特的属性 可能是它的输出可以被证明服从一些可枚举的 生成规则。例如,对于任何特定的 HTML 程序(忽略 解释者分歧方式的差异) ,这是没有脚本 (即不是图灵完成) ,那么它的输出可变性可以是 或者更简单地说,一个 HTML 程序是一个纯函数 和电子表格程序一样,它的纯粹功能是 输入变量 所以在我看来,声明性语言是 无界递归无界递归,也就是按照哥德尔的第二个不完全性 定理自指定理不能证明 莱西 · 兰波特 写的一个关于欧几里得如何可能拥有的童话故事 围绕哥德尔应用于数学证明的不完全性定理进行研究 在编程语言上下文中,通过使类型和 逻辑(柯里-霍华德同构等)
这取决于你如何提交文本的答案。总的来说,你可以从某个角度来看这个节目,但这取决于你从哪个角度来看问题。我会让你开始这个项目: 昏暗的公共汽车,汽车,时间,高度作为整数 同样,这取决于问题是什么。由于节目的关系,你可能不得不缩短时间。希望这有所帮助,如果没有,需要反馈。 谢谢

举个例子。

在 CSS (用于设计 HTML 页面)中,如果你想要一个图像元素高100像素,宽100像素,你只需要像下面这样“声明”它就是你想要的:

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

您可以将 CSS 视为声明性的“样式表”语言。

浏览器引擎读取和 翻译这个 CSS 是免费的,使图像显示这个高度和这个宽度,只要它想要的。不同的浏览器引擎(例如 IE 的引擎,Chrome 的引擎)会以不同的方式执行这个任务。

当然,它们独特的实现不是用声明性语言编写的,而是用汇编、 C、 C + + 、 Java、 JavaScript 或 Python 等过程性语言编写的。这段代码是一组要逐步执行的步骤(可能包括函数调用)。它可能会做一些事情,比如插值像素值,并在屏幕上渲染。