编程中声明式和命令式范式有什么区别?

我一直在网上搜索陈述性当务之急编程的定义,这对我有一些启发。然而,我发现的一些资源中使用的语言令人望而生畏-例如在维基百科。 有没有人可以向我展示一个真实世界的例子,可以为这个主题带来一些视角(也许在C#中)?

315716 次浏览

声明式编程是当你说什么你想要的,命令式语言是当你说如何得到你想要的。

Python中的一个简单示例:

# Declarative
small_nums = [x for x in range(20) if x < 5]


# Imperative
small_nums = []
for i in range(20):
if i < 5:
small_nums.append(i)

第一个例子是声明性的,因为我们没有指定构建列表的任何“实现细节”。

结合C#的例子,通常使用LINQ会导致声明式风格,因为你不是说如何来获得你想要的;你只是说什么你想要的。你可以对SQL说同样的话。

声明式编程的一个好处是它允许编译器做出决策,这些决策可能会导致比您手工编写的代码更好的代码。使用SQL示例运行,如果您有一个查询,如

SELECT score FROM games WHERE id < 100;

SQL“编译器”可以“优化”这个查询,因为它知道id是一个索引字段——或者它可能没有索引,在这种情况下,它无论如何都必须迭代整个数据集。或者SQL引擎知道这是利用所有8个内核进行快速并行搜索的最佳时机。作为程序员,不关心任何这些条件,你不必编写代码来以这种方式处理任何特殊情况。

声明式编程与命令式编程的一个很好的C#示例是LINQ。

使用当务之急编程,您可以一步一步地告诉编译器您想要发生的事情。

例如,让我们从这个集合开始,并选择奇数:

List<int> collection = new List<int> { 1, 2, 3, 4, 5 };

通过命令式编程,我们将逐步完成这一点,并决定我们想要什么:

List<int> results = new List<int>();
foreach(var num in collection)
{
if (num % 2 != 0)
results.Add(num);
}

在这里,我们说:

  1. 创建结果集合
  2. 逐步遍历集合中的每个数字
  3. 检查数字,如果是奇数,将其添加到结果中

另一方面,使用陈述性编程,您编写的代码描述了您想要的东西,但不一定是如何获得它(声明您想要的结果,而不是一步一步):

var results = collection.Where( num => num % 2 != 0);

在这里,我们说“给我们所有奇怪的地方”,而不是“逐步遍历集合。检查此项,如果它是奇数,则将其添加到结果集合中。”

在许多情况下,代码也将是两种设计的混合物,因此它并不总是黑白的。

在计算机科学中,声明式编程是一种编程范式,它表达了计算的逻辑,而不描述其控制流。

来自http://en.wikipedia.org/wiki/Declarative_programming

简而言之,声明性语言更简单,因为它缺乏控制流(循环、if语句等)的复杂性。

一个很好的比较是ASP. Net的“代码隐藏”模型。您有声明式的“. ASPX”文件,然后是命令式的“ASPX.CS”代码文件。我经常发现,如果我能在脚本的声明式部分完成所有我需要的工作,那么更多的人可以遵循正在完成的工作。

命令式编程明确地告诉计算机该做什么以及如何做,例如指定顺序等

C#:

for (int i = 0; i < 10; i++)
{
System.Console.WriteLine("Hello World!");
}

声明式是指你告诉计算机该做什么,但不知道该怎么做。Datalog/Prolog是这方面首先想到的语言。基本上一切都是声明式的。你不能真正保证顺序。

C#是一种更命令式的编程语言,但某些C#特性更具声明性,例如Linq

dynamic foo = from c in someCollection
let x = someValue * 2
where c.SomeProperty < x
select new {c.SomeProperty, c.OtherProperty};

同样的事情也可以写成势在必行的:

dynamic foo = SomeCollection.Where
(
c => c.SomeProperty < (SomeValue * 2)
)
.Select
(
c => new {c.SomeProperty, c.OtherProperty}
)

(来自wikipedia Linq)

我将添加另一个在声明式/命令式编程讨论中很少出现的示例:用户界面!

在C#中,您可以使用各种技术构建UI。

在命令式的一端,您可以使用DirectX或OpenGL非常命令式地逐行绘制您的按钮、复选框等(或者实际上,一个三角形一个三角形)。

在声明性端,您有WPF。您基本上编写了一些XML(是的,是的,技术上是“XAML”),框架为您完成工作。您说用户交互界面是什么样子的。由系统来弄清楚如何做到这一点。

不管怎样,还有一件事要考虑。仅仅因为一种语言是陈述式或命令式的,并不意味着它没有另一种语言的某些特征。

此外,声明式编程的一个好处是,目的通常更容易通过阅读代码来理解,而命令式可以让您更好地控制执行。

这一切的要点:

声明式->what你想要完成

命令式->how你想要它完成

宣告与命令

A编程范式是计算机编程的基本风格。 有四种主要范式:命令式、声明式、函数式(被认为是声明式范式的子集)和面向对象。

声明式编程:是一种编程范式,它表达了计算的逻辑(What do),而不描述其控制流(How do)。 声明性领域特定语言(DSL)的一些著名示例包括CSS、正则表达式和SQL的子集(例如SELECT查询) 许多标记语言,如超文本标记语言、MXML、XAML、XSLT……通常是声明性的。 声明式编程试图模糊程序作为一组指令和程序作为关于所需答案的断言之间的区别。

命令式编程:是一种编程范式,它根据更改程序状态的语句来描述计算。命令式程序可以双重视为编程命令或数学断言。

函数式编程:是一种将计算视为数学函数的求值,避免状态和可变数据的编程范式,它强调函数的应用,与命令式编程风格相反,命令式编程风格强调状态的变化。 在纯函数式语言中,例如Haskell,所有函数都没有副作用,状态更改仅表示为转换状态的函数。

下面是MSDN中命令式编程的示例,遍历数字1到10,并找到偶数。

var numbersOneThroughTen = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//With imperative programming, we'd step through this, and decide what we want:
var evenNumbers = new List<int>();
foreach (var number in numbersOneThroughTen)
{    if (number % 2 == 0)
{
evenNumbers.Add(number);
}
}
//The following code uses declarative programming to accomplish the same thing.
// Here, we're saying "Give us everything where it's even"
var evenNumbers = numbersOneThroughTen.Where(number => number % 2 == 0);

两个例子产生相同的结果,其中一个并不比另一个更好或更差。第一个例子需要更多代码,但代码是可测试的,并且命令式方法使你可以完全控制实现细节。在第二个例子中,代码可以说更具可读性;然而,LINQ并不能让你控制幕后发生的事情。你必须相信LINQ会提供请求的结果。

再举一个移动应用程序开发的例子,在iOS和Android中,我们有Interface Builder,我们可以在其中定义应用程序的UI。

使用这些Builders绘制的UI本质上是声明性的,我们在其中拖放组件。实际的绘图发生在下面并由框架和系统执行。

但我们也可以在代码中绘制整个组件,这在本质上是必要的。

此外,像Angular JS这样的一些新语言专注于以声明方式设计UI,我们可能会看到许多其他语言提供相同的支持。就像Java没有任何好的声明方式来绘制Java摇摆或JavaFX中的原生桌面应用程序,但在不久的将来,它们可能会。

命令式编程要求开发人员逐步定义如何 代码应该被执行。以命令式的方式给出指示, 你说,“去第一街,左转进入Main,开两个街区, 右转进入枫树,在左边的第三栋房子停下来。” 声明式版本可能听起来像这样:“Drive to Sue's “一个说如何做某事;另一个说需要做什么 完成。

声明式风格与命令式风格相比有两个优点:

  • 它不会强迫旅行者记住一长串指令。
  • 它允许旅行者在可能的情况下优化路线。

卡尔弗特,C Kulkarni,D(2009年)。基本LINQ。艾迪生·韦斯利。48。

我是菲利普·罗伯茨窃取:

  • 命令式编程告诉机器如何做某事(导致你想要发生的事情)
  • 声明式编程告诉机器你想发生什么(计算机会弄清楚如何做到这一点)

两个例子:

1.将数组中的所有数字加倍

当务之急:

var numbers = [1,2,3,4,5]
var doubled = []


for(var i = 0; i < numbers.length; i++) {
var newNumber = numbers[i] * 2
doubled.push(newNumber)
}
console.log(doubled) //=> [2,4,6,8,10]

声明:

var numbers = [1,2,3,4,5]


var doubled = numbers.map(function(n) {
return n * 2
})
console.log(doubled) //=> [2,4,6,8,10]

2.汇总列表中的所有项目

当务之急

var numbers = [1,2,3,4,5]
var total = 0


for(var i = 0; i < numbers.length; i++) {
total += numbers[i]
}
console.log(total) //=> 15

声明

var numbers = [1,2,3,4,5]


var total = numbers.reduce(function(sum, n) {
return sum + n
});
console.log(total) //=> 15

注意命令式示例如何涉及创建一个新变量,改变它并返回新值(即如何使某事发生),而声明式示例在给定输入上执行并根据初始输入返回新值(即我们想要发生的事情)。

我喜欢剑桥课程的解释+他们的例子:

  • 陈述性-指定什么要做,不是如何要做
    • 例如:超文本标记语言描述的是网页上应该显示的内容,而不是屏幕上应该如何绘制
  • 当务之急-指定什么如何
    • int x;-什么(声明式)
    • x=x+1;-如何

根据我的理解,这两个术语都源于哲学,有陈述性和命令式的知识。陈述性知识是对真理的断言,像数学公理一样的事实陈述。它告诉 命令式,或程序知识,一步一步地告诉你如何到达某事。这就是算法的本质定义。如果你愿意,将计算机编程语言与英语进行比较。声明式句子说明了一些东西。一个无聊的例子,但这里有一种声明式的方式来显示两个数字是否彼此相等,在Java:

public static void main(String[] args)
{
System.out.print("4 = 4.");
}

另一方面,英语中的命令式句子给出命令或发出某种请求。那么,命令式编程只是命令列表(做这个,做那个)。这是在接受用户输入时显示两个数字是否相等的命令式方法,在Java中:

private static Scanner input;


public static void main(String[] args)
{
input = new Scanner(System.in);
System.out.println();
System.out.print("Enter an integer value for x: ");
int x = input.nextInt();
System.out.print("Enter an integer value for y: ");
int y = input.nextInt();


System.out.println();
System.out.printf("%d == %d? %s\n", x, y, x == y);
}

本质上,声明式知识跳过某些元素以在这些元素上形成抽象层。声明式编程也一样。

命令式编程
一种需要编程纪律的编程语言,如C/C++、Java、COBOL、FORTRAN、Perl和JavaScript。使用这些语言编写的程序员必须根据数据处理和编程知识制定正确的操作顺序来解决问题。

声明式编程
一种不需要编写传统编程逻辑的计算机语言; 用户专注于定义输入和输出,而不是过程编程语言(如C++或Java)所需的程序步骤。

声明式编程示例有CSS、超文本标记语言、XML、XSLT、RegX。

声明式程序只是其或多或少“通用”命令式实现/vm的数据。

优点: 仅仅指定一个数据,以某种硬编码(和检查)格式,比直接指定一些命令式算法的变体更简单,更不容易出错。一些复杂的规范不能直接编写,只能以一些DSL形式编写。 因为元素/行之间没有依赖关系,当你没有依赖关系时,你有修改的自由和易于支持。(比较例如模块与类-与你喜欢的模块和与你有脆弱基类问题的类) 声明性和DSL的所有好处都直接来自该数据结构(表和集)的好处。 另一个优点-您可以更改声明性语言vm的实现,如果DSL或多或少是抽象的(设计良好)。例如,进行并行实现。或将其移植到其他操作系统等。所有良好的专用模块化隔离接口或协议都为您提供了这种支持的自由度和易用性。

缺点: 你猜对了。在某些情况下,通用(并由DSL参数化)命令式算法/vm实现可能比特定的实现慢和/或内存消耗。 如果这种情况下是罕见的-只是忘记它,让它慢.如果它是f的-你总是可以扩展你的DSL/虚拟机的情况下.某处放慢所有其他情况下,当然…

P. S.框架介于DSL和必要之间。作为所有的中途解决方案…他们结合了不足,而不是好处。他们不太安全,也不太快:)看看万事通haskell——它介于强大的简单ML和灵活的元程序Prolog之间…这是一个多么巨大的怪物。你可以把Prolog看做一个只有布尔函数/谓词的Haskell。以及它对Haskell的灵活性有多简单…

我只是想知道为什么没有人提到属性类作为C#中的声明式编程工具。本页的流行答案刚刚谈到LINQ是一种声明式编程工具。

根据维基百科

常见的声明性语言包括数据库查询语言 (例如,SQL,XQuery),正则表达式,逻辑编程, 函数式编程和配置管理系统。

所以LINQ作为一种函数式语法,绝对是一种声明式方法,但C#中的属性类作为一种配置工具,也是声明式的。这里是阅读更多信息的一个很好的起点:C#属性编程的快速概述

这里和其他在线帖子中的答案提到了以下内容:

  • 使用陈述性编程,您编写的代码描述了您想要的东西,但不一定是如何获得它
  • 与命令式编程相比,您应该更喜欢声明式编程

他们没有告诉我们的是如何实现它。为了使程序的一部分更具声明性,其他部分必须提供抽象来隐藏实现细节(即当务之急代码)。

  • 例如,LINQ比循环更具声明性(for、这时候等),例如,你可以使用list.Where()来获取一个新的过滤列表。为了做到这一点,微软已经完成了LINQ抽象背后的所有繁重工作。

事实上,函数式编程和函数式库更具声明性的原因之一是因为它们抽象了循环和列表创建,将所有实现细节(很可能是带有循环的命令式代码)隐藏在幕后。

在任何程序中,您都将始终拥有命令式和声明式代码,您应该将所有当务之急代码隐藏在领域特定抽象后面,以便程序的其他部分可以使用它们声明性

最后,尽管函数式编程和LINQ可以使您的程序更具声明性,但您始终可以通过提供更多抽象来使其更具声明性。例如:

// JavaScript example


// Least declarative
const bestProducts = [];
for(let i = 0; i < products.length; i++) {
let product = products[i];
if (product.rating >= 5 && product.price < 100) {
bestProducts.push(product);
}
}




// More declarative
const bestProducts = products.filter(function(product) {
return product.rating >= 5 && product.price < 100;
});


// Most declarative, implementation details are hidden in a function
const bestProducts = getBestProducts();

P. S.声明式编程的极端是发明新的领域特定语言(DSL):

  1. 字符串搜索:正则表达式而不是自定义命令式代码
  2. React.js:JSX而不是直接的DOM操作
  3. AWS云计算:YAML而不是CLI
  4. 关系数据库:SQL而不是旧的读写API,例如ISAM或VSAM。

差异主要与抽象的总体水平有关。对于声明式,在某些时候,你离单个步骤太远了,以至于程序在如何获得结果方面有很大的自由度。


你可以把每一条指令看作是一个连续体的某个地方:

抽象程度:

Declarative <<=====|==================>> Imperative

声明式现实世界示例:

  1. 图书管理员,请检查我的白鲸的副本。 (图书管理员自行选择执行请求的最佳方法)

现实世界的例子:

  1. 走进图书馆
  2. 查找图书组织系统(卡片目录-旧学校)
  3. 研究如何使用卡片目录(你也忘了,对吧)
  4. 弄清楚货架是如何标记和组织的。
  5. 弄清楚书架上的书是如何组织的。
  6. 从卡片曲库与组织系统的交叉参考书位置找到所述书。
  7. 把书带到退房系统。
  8. 看看这本书。

命令式编程-您编写完成工作的代码

声明式编程-其他人编写完成工作的代码

已经添加了很多代码示例,所以我不会再添加一个。
相反,我将尝试以一种我认为使其本质比大多数浮动定义更清晰的方式解释这两种方法之间的区别:

  • 陈述性方法侧重于特定算法的目的,这通常会隐藏算法本身。

  • 当务之急方法专注于用于特定目的的算法,这通常会隐藏目的本身。

我发现基于幂等交换区分声明式和命令式更容易。使用引用来了解它们。

Checkout<强>这个简化版了解幂等。

然后我引入"什么"&"怎么"的定义来理解"什么"&"怎么"的实际含义。在声明式中,通过定义它们之间的关系将一个数据与另一个数据连接起来。你没有提到应该如何实现这种关系,而是“什么”那种关系。通过你描述“什么”的关系,你的输出数据看起来像“如何”来实现这种输出数据。

开始在我们的脑海中画一些图表,画一些点(数据)并用线连接它们(关系)。以各种可能的方式画一对多,多对一和一对一。给这些线加上箭头,像这样<;-----------. 所有箭头都应该向左,因为必须首先计算特定数据所基于的所有数据,然后向左移动以计算该特定数据。

如果数据a基于数据b、数据c和数据d,而EYZ3又可能基于其他数据。那么应该首先计算bcd,然后才计算a。因此a在线的左侧,而其他所有线在右侧。将有3条线从bcd中的每一条到达a

这个图有一些属性:

  • 没有数据会破坏它与所有其他数据的关系
  • 控制流或顺序并不重要,当然bcd应该在a之前计算,但bcd之间没有偏好,即这3个中的哪一个首先计算并不重要(交换
  • a仅基于bcd,而没有其他基础。因此,无论执行多少次使用bcd计算a的关系操作,都应该实现相同的ab2)。a是这里关系操作的最终结果。基本上,每个影响a的人都应该有一条线指向a

这些关系(行)就像功能(数学函数和不是编程)。毫无疑问,函数al编程在学术界很有名。纯函数(我们的编程,因此不用粗体)就像功能(数学,因此在大胆中)。

到目前为止,声明式可能已经开始听起来像PURE和IMMUTABLE(通常用于函数al编程),如果是好的,如果不是好的。因为这不是这里的目标,这是自动从这个模式中出现的东西。

如果您的代码片段可以转换为此图,那么它就是完全陈述性,否则它位于标度的其他位置。

声明式接近于数学

现在让我们放大这些关系(线),看看程序执行期间计算机内部发生了什么。

命令式进来了。这是完成基础工作的地方。在命令式中,你提到一步一步“如何”它需要完成,你知道这一系列步骤将在一个数据(输入bcd)和另一个数据(输出a)之间创建请求的关系。在这里你创建变量,突变它们,循环遍历数组和所有其他事情。

命令式接近编程

与其说一个程序是声明式的或命令式的,我更喜欢在一个标度上看到它,最左边是完全陈述性,最右边是完全必要。记住,声明式是建立在命令式之上的,因此你看到的任何声明式的东西实际上在下面都是命令式的。通常,程序是声明式和命令式的混合体。

现在,让我们看看这些2个例子

第二个例子可以转换成这样的图表:

reduce_rmap_rfilter_r
a<;--------- b<;--------- c<;--------- d

  • filter_r(关系):c只是d的偶数
  • map_r(关系):b是所有数字乘以10的c
  • reduce_r(关系):ab相加的所有数字

这应该看起来像复合函数数学:reduce_rmap_rfilter_rd)))

在声明式中,开发人员的工作是将最终目标(a)分解为子目标(bc),这将有助于实现最终目标。

当然,在程序地图减少过滤器的引擎盖下运行的是命令式代码。

思考的食物:如果你需要对map函数做一个从左到右的假设,以使你的代码按预期工作,你实际上是在以声明的名义做命令式。

参考文献:Purpleidea(詹姆斯)www.dataops.livewiki.c2.com

只是一个实际的例子,为什么css陈述性javascript当务之急

假设我们有这个导航栏,用户当前正在查看“探索”选项,因此它被标记为当前已选择。

输入图片描述

<ul>
<li class="selected">
<p>Explore</p>
</li>
<li>
<p>Suggestions</p>
</li>
</ul>

我们希望当前选择的选项的标题是蓝色的,我们如何使用CSS和JavaScript实现这一点?

css

li.selected > p {
color: blue;
}

这里li.selected > p声明了我们希望应用属性color: blue;的元素模式。结果是“探索”以蓝色突出显示,但“建议”没有。请注意,我们的代码描述了我们想要发生的事情,而不是如何。CSS选择器引擎如何找到“探索”?我们不知道,通常也不在乎。

javascript

let liElements = document.getElementsByTagName("li")
for (let i = 0; i < liElements.length; i++) {
if (liElements[i].className === "selected") {
let children = liElements[i].childNodes
for (let j = 0; j < children. length; j++) {
let child = children[j]
if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "P") {
child.setAttribute("style", "color: blue")
}
}
}
}

这段代码更长,更难理解。除此之外,它将蓝色应用于所选选项,但在删除selected类时永远不会取消应用它。蓝色仅在重新加载页面时重置。请注意,使用这段代码,我们准确地指定了需要做什么和如何,一步一步

结论

每个编程范例都带来了它的优势。

CSS(声明式)

  • 简洁
  • 作为程序员,我们无法控制CSS核心如何满足我们的需求。这给了CSS核心开发人员一个随时更改CSS选择器实现的机会。为什么CSS核心需要更改?也许,CSS开发人员找到了一种更快的应用属性的方法。

JavaScript(命令式)

  • 定制。我们控制代码如何实现目标的所有方面。
  • 善于解决各种各样的问题