什么是陈述式编程?

我一直听到这个词在几种不同的情况下被抛出。 它是什么?

声明式编程就是当你编写你的代码时,它描述了你想要做的事情,而不是你想做什么。 由编译器决定如何。

声明式编程语言的例子是SQL和Prolog。

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

上下文独立

声明程序是上下文无关的 。 因为他们只是宣布最终目标是什么,而不是实现这个目标的中间步骤,同一个程序可以用在不同的环境中。 这与命令式程序很难做到,因为它们通常依赖于上下文(例如隐藏状态)。

yacc为例。 这是一个parsing器生成器。 编译器编译器,一个用于描述语言语法的外部声明式DSL,从而可以从描述中自动生成该语言的parsing器。 由于语境的独立性,你可以用这样的语法做很多不同的事情:

  • 为该语法生成​​一个C语言分析器( yacc的原始用例)
  • 为该语法生成​​一个C ++parsing器
  • 为该语法生成​​Javaparsing器(使用Jay)
  • 为该语法生成​​一个C#parsing器(使用GPPG)
  • 为该语法生成​​一个Rubyparsing器(使用Racc)
  • 生成该语法的树形图(使用GraphViz)
  • 简单地做一些漂亮的打印,幻想格式和yacc源文件本身的语法突出显示,并将其包含在您的参考手册中作为您的语言的语法规范

还有很多 …

优化

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

在这里有一个有趣的折衷:机器必须更努力地弄清楚如何做一些事情,而不是一个命令式的语言,但是当它发现它时,它有更多的自由和更多的信息来优化阶段。

我很抱歉,但我不同意其他许多答案。 我想停止这种对声明性编程定义的混淆误解。

定义

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

声明式编程的其他引用属性来自这个RT。 请点击上面的超链接进行详细解释。

电子表格示例

两个答案提到电子表格编程。 在电子表格编程(又名公式)不能访问可变全局状态的情况下,则是声明编程。 这是因为可变单元值是“main()”(整个程序)的单片input输出 。 在每个公式执行完成后,新的值不会写入到单元格中,因此它们在声明性程序(电子表格中所有公式的执行)中是不可变的。 因此相对于彼此,这些公式将这些可变单元视为不可变的。 RT函数被允许访问不可变的全局状态(也是可变的本地状态 )。

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

松散:

声明式编程倾向于: –

  • 一组声明或声明性声明,每个声明都有含义(通常在问题域中),并且可以被独立和孤立地理解。

势在必行的编程趋向于: –

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

因此,一种必要的风格可以帮助读者理解系统实际上在做什么的机制,但是可能对它打算解决的问题没有深入的了解。 另一方面,陈述式风格有助于读者理解问题领域和系统解决问题的方法,但在力学问题上信息量较less。

真正的程序(甚至是那些赞成频谱末端的语言,如ProLog或C)都倾向于在不同程度上呈现出不同程度的风格,以满足作品不断变化的复杂性和通信需求。 一种风格不优于另一种; 他们只是服务于不同的目的,而且和生活中的许多事情一样,适度是关键。

声明式编程就是这样一种图像,其中命令式编程就是绘制该图像的指令。

如果你是“告诉它是什么”,而不是描述计算机应该到达你想要的位置的步骤,那么你就是用声明式的风格写作的。

当你使用XML来标记数据时,你使用的是声明式编程,因为你在说“这是一个人,那是一个生日,在那里有一个街道地址”。

声明式和命令式编程得到组合的一些例子得到了更好的效果:

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

  • 结构化configuration文件使用声明性语法(与“key = value”对一样简单)来标识数据的string或值的含义。

  • HTML标记带有标签的文本,描述每段文本与整个文档的关系。

想象一个excel页面。 列填充公式来计算你的纳税申报表。

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

这就是声明式编程的全部内容。 你声明问题空间和解决scheme,而不是程序的stream程。

Prolog是我唯一使用的声明性语言。 它需要一种不同的思维方式,但是如果只是为了向你展示除了典型的过程式编程语言以外的其他东西,那么学习也是很好的。

自从我写了我以前的答复以来,我已经在下面引用了一个新的声明性的定义 。 我也将命令式编程定义为双重属性。

这个定义比我在前面的答案中提供的定义要好,因为它简洁而且更一般。 但是可能更难以理解,因为适用于编程和生活的不完备性定理的含义一般难以让人思考。

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

说明性与必要性

声明性属性是奇怪的,钝的,并且难以在技术上精确的定义中捕捉,这个定义仍然是一般的而不是含糊不清的,因为我们可以声明程序的含义(又称语义)而不会产生意想不到的副作用,这是一个天真的想法。 意义的expression与避免意想不到的效果之间存在内在的张力,而这种张力实际上是由程序和我们的宇宙的不完备定理导出的。

这是过于简单化,技术上不精确,而且往往模棱两可的定义为做什么如何做 ”的必要性。 一个模棱两可的情况是在输出一个程序的程序中“ 怎样 ”是一个编译器。

显然,使语言图灵完整的无限recursion也类似于语义 – 不仅在评估的语法结构(也就是操作语义)中。 这在逻辑上就是类似于哥德尔定理的一个例子 – “ 任何完整的公理系统也是不一致的 ”。 思考那个引用的矛盾奇怪! 这也是一个例子,说明语义的expression如何不具有可certificate的界限,因此我们不能certificate程序(类似地,它的语义)停止了又叫停止定理。

不完全性定理是从我们的宇宙的基本性质中得出的,正如热力学第二定律所说的那样:“ (也就是独立可能性的可能性) 正趋向于永远的最大值 ”。 程序的编码和devise从来没有完成 – 它还活着! – 因为它试图解决现实世界的需要,现实世界的语义总是在变化,并趋向于更多的可能性。 人类永远不会停止发现新事物(包括程序中的错误;-)。

为了在这个没有边缘的怪异宇宙(精确地说,没有宇宙的“外部”)中精确地和技术上地捕捉到上述所期望的概念,需要一个简洁但看似非简单的定义,直到解释深。

定义:


声明性属性是只能存在一组可能expression每个特定模块化语义的语句的地方。

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


这种声明的定义在语义范围上是独特的局部的 ,这意味着它要求模块化的语义保持其一致的含义,而不pipe它在全球范围内如何实例化和使用。 因此,每个说明性的模块化语义应该与所有可能的其他语言本质上是正交的,而不是一个不可能的(由于不完备性定理) 全局algorithm或者一致性见证模型,这也是罗伯特·哈珀教授的“ 更多并不总是更好 ”卡内基梅隆大学计算机科学硕士,标准ML的devise师之一。

这些模块化的声明性语义的例子包括类别理论函子,例如Applicative ,名义types,命名空间,命名字段,以及操作级别的语义,然后是纯函数式编程。

因此,devise良好的陈述式语言可以更清楚地expression意义 ,虽然可以expression某些内容的普遍性有些丧失,但是可以用内在的一致性来expression。

上述定义的一个例子是电子表格程序的单元格中的一组公式,当移动到不同的列和行单元格时,不期望赋予相同的含义,即单元格标识符改变。 小区标识符是预期含义的一部分而不是多余的。 因此,每个电子表格结果对于一组公式中的单元格标识符是唯一的。 在这种情况下,一致的模块语义是使用单元格标识符作为单元格公式的函数的input和输出(见下文)。

超文本标记语言又名HTML–静态网页的语言 – 是一个高度(但不是完美的)声明性语言(至less在HTML 5之前)不能expressiondynamic行为的例子。 HTML也许是最容易学习的语言。 对于dynamic行为,JavaScript等命令式脚本语言通常与HTML结合使用。 没有JavaScript的HTML符合声明性的定义,因为每个名义types(即标签)在语法规则的组合下保持其一致的含义。

声明性的竞争定义是语义陈述的交换和幂等性,也就是说陈述可以在不改变意义的情况下重新sorting和复制。 例如,赋值给指定字段的语句可以被重新sorting和复制,而不会改变程序的含义,如果这些名称是模块化的,则隐含顺序。 名称有时意味着一个命令,例如单元格标识符包括它们的列和行的位置 – 在电子表格上移动总数会改变它的含义。 否则,这些属性隐含地要求语义的全局一致性。 devise语句的语义通常是不可能的,所以如果它们是随机sorting或者重复的话,它们将保持一致,因为顺序和重复是语义所固有的。 例如,陈述“Foo存在”(或build筑)和“Foo不存在”(和破坏)。 如果考虑到目标语义的随机不一致性,那么我们接受这个定义对于声明性属性是足够一般的。 实质上,这个定义是一个空泛的定义,因为它试图使一致性与语义正交,也就是说,不要把这个语义的宇宙是dynamic的无界的,不能在全局的连贯范式中被捕获的。

要求低级操作语义(的结构评估顺序)的交换和幂等属性将操作语义转换为声明性本地化模块化语义,例如函数式编程(包括recursion而不是命令式循环)。 那么实现细节的操作顺序不会影响(即, 全局扩展)更高级语义的一致性。 例如,电子表格公式的评估(理论上也是重复)的顺序并不重要,因为在计算完所有输出之后,输出不复制到input,即类似纯函数。

C,Java,C ++,C#,PHP和JavaScript并不是特别的声明。 Copute的语法和Python的语法更加明确地与预期的结果相结合 ,即一致的语法语义,消除了无关的因此人们可以在忘记它之后轻松地理解代码。 Copute和Haskell强制操作语义的决定论,鼓励“ 不要重复自己 ”(DRY),因为它们只允许纯粹的function范式。


2即使我们可以certificate一个程序的语义,例如使用Coq语言,这也仅限于在打字中expression的语义,打字永远不能捕捉程序的所有语义,甚至不能捕捉语言不是图灵完整的,例如用HTML + CSS,可以expression不一致的组合,因此具有未定义的语义。

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


编辑:我发表了以下评论罗伯特·哈珀的博客:

在函数式编程中…variables的变化范围是一种types

根据一个人如何将function区别于命令式编程,你在一个命令式程序中的“可转让”也可能具有一种对其变异性有约束力的types。

我目前对函数式编程唯一没有混淆的定义是:a)作为第一类对象和types,b)在循环上优先recursion,和/或c)纯函数 – 即那些不影响想要的语义的函数( 因此,由于操作语义的影响,例如存储器分配,完全纯粹的函数式编程不存在于通用指称语义中 )。

纯函数的幂等性意味着对其variables的函数调用可以用它的值来代替,这对于命令过程的参数通常不是这样。 纯函数似乎是对input和结果types之间的未分解状态转换的声明。

但是纯函数的组成并不能保持任何这样的一致性,因为有可能在一个纯粹的函数式编程语言(例如Haskell的IOMonad)中build立一个副作用(全局状态)命令过程,而且完全不可能防止这样做任何图灵完备的纯函数式编程语言。

正如我在2012年撰写的那样,在你最近的博客中似乎有类似的意见一致意见,那个声明性编程就是试图捕捉这样一个概念,即预期的语义从来就不是不透明的。 不透明语义的例子是依赖于顺序,依赖于在操作语义层删除高级语义(例如, 强制types不是转换,泛化generics限制高级语义 ),依赖于不能被检查的variables值正确)由编程语言。

因此我得出结论:只有非图灵完备的语言才是可以声明的。

因此,声明性语言的一个明确而独特的属性可能是它的输出可以被certificate服从一些无数的生成规则。 例如,对于任何特定的HTML程序(忽略解释者分歧的方式中的差异),不是脚本化的(即不是图灵完整的),则其输出变化性可以是可枚举的。 或者更简洁地说,HTML程序是其变化的纯粹function。 同样,电子表格程序是其inputvariables的纯函数。

所以在我看来,声明性语言是无限recursion的对立面,即根据哥德尔的第二个不完备性定理,自我指称定理不能被certificate。

Lesie Lamport 写了一个关于欧几里德如何在编程语言环境中将哥德尔(Gödel)的不完备性定理应用于mathcertificate的类似逻辑(Curry-Howard通信等)之间的一致性的童话故事。

这是一种基于描述什么应该做或者应该做什么而不是描述应该如何工作的编程方法。

换句话说,你不会写出由expression式构成的algorithm,而只是布置你想要的东西。 两个很好的例子是HTML和WPF。

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

自2011年12月我提供了这个问题的答案以来,我已经提炼了我对声明性编程的理解。 这里遵循我目前的理解。

我的理解(研究)的长版本在这个链接上有详细说明,您应该阅读以深入理解我将在下面提供的总结。

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

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

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

DP更有用的定义是编程指令的sorting和/或重复不会改变任何不透明的语义。 换句话说,在语义发生时不会有任何隐藏的随机变化 – 程序指令顺序和/或重复的任何变化只会导致程序行为的有意和透明的变化。

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

声明式编程是用声明进行编程,即声明性句子。 陈述性句子有许多将它们与命令性句子区分开来的属性。 具体来说,声明是:

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

相关的一点是,这些都是结构性质,并与主题是正交的。 声明不是关于“什么与如何” 。 我们可以像声明“what”一样轻松地声明(表示和约束) “how 声明是关于结构,而不是内容。 声明式编程对我们如何抽象和重构我们的代码有重大的影响,以及如何将其模块化为子程序,而不是在域模型上。

通常情况下,我们可以通过添加上下文从强制转换为声明。 例如,从“向左转(等待…)向右转”。 到“Bob将在11:01在Foo和Bar的交叉点左转,Bob将在11:06在Bar和Baz的交叉点右转。” 请注意,在后一种情况下,句子是幂等和交换的,而在前一种情况下,重新排列或重复这些句子将严重改变程序的含义。

关于单调性 ,声明可以添加减less可能性的 约束条件 。 但约束仍然增加了信息(更确切地说,约束是信息)。 如果我们需要随时间变化的声明,那么典型的做法是用明确的时间语义进行build模,例如从“球是平的”到“球在时间T是平坦的”。 如果我们有两个相互矛盾的声明,那么我们就有一个不一致的声明系统,尽pipe这可以通过引入约束(优先级,概率等)或者利用并行逻辑来解决。

这是一个例子。

在CSS中(用于设置HTML页面的样式),如果你想让一个图像元素的高度为100像素,宽度为100像素,那么你只需要“声明”这就是你想要的,如下所示:

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

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

读取和解释这个CSS的浏览器引擎可以自由地让图像出现在这个高而宽的地方。 不同的浏览器引擎(例如,IE引擎,Chrome引擎)将以不同的方式执行此任务。

当然,它们的独特实现不是用声明性语言编写的,而是用程序集编写的,如C,C ++,Java,JavaScript或Python。 这段代码是一大堆步骤(可能包括函数调用)。 它可能会执行像插值像素值,并在屏幕上呈现。

向电脑描述你想要什么,而不是如何去做。

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

因为DP是一种expression方式,所以我会解释它

  • 目标expression ,我们正在寻找的条件。 有一个,也许还是很多?
  • 一些已知的事实
  • 延伸知情事实的规则

…以及通常使用统一algorithm来寻找目标的扣除引擎。

声明式编程是“符合开发人员心智模式的语言编程,而不是机器的操作模式”。

parsing结构化数据的问题很好地说明了声明式和命令式编程之间的区别。

命令式程序将使用相互recursion的函数来消费input和生成数据。 声明性程序将expression一个定义数据结构的语法,从而可以对其进行分析。

这两种方法之间的区别在于,声明式程序创build了一种新的语言,它比主语更贴近问题的心理模型。

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

它越来越意味着很less,因为它具有上面用户给出的定义。 应该清楚的是,Haskell的声明性编程与HTML的声明性编程之间存在一个鸿沟。

声明式编程的另外几个例子:

  • 数据绑定的ASP.Net标记。 例如,它只是说“用这个来源填充这个网格”,然后把它留给系统来处理。
  • Linqexpression式

声明式编程是很好的,因为它可以帮助简化代码的心智模型 ,并且因为它最终可能更具可扩展性。

例如,假设您有一个函数对数组或列表中的每个元素执行一些操作。 传统的代码如下所示:

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

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

 MyList.ForEach(DoSometing); 

这当然更简洁。 但是我确定你有更多的担心,而不是只是在这里和那里保存两行代码。 性能,例如。 旧的方式,处理必须按顺序进行。 如果.ForEach()方法有办法让你发出信号,它可以自动处理并行处理? 现在突然之间,您已经以非常安全的方式使您的代码变成了multithreading,并且只更改了一行代码。 事实上,.Net有一个扩展 ,可以让你做到这一点。

  • 如果你按照这个链接,它会把你带到我的一个朋友的博客文章。 整个post有点长,但是你可以向下滚动到标题为 “The Problem” 的标题,并把它拿起来没问题。

这取决于你如何提交文本的答案。 总体而言,您可以在某个视图中查看该程序,但这取决于您查看问题的angular度。 我会让你开始使用这个程序:Dim Bus,Car,Time,Height As Integr

这又取决于整个问题是什么。 由于该程序,您可能不得不缩短它。 希望这有助于并需要反馈,如果没有。 谢谢。

Interesting Posts