声明式和命令式编程之间有什么区别?[关闭]


541

我一直在网上寻找声明式和命令式编程的定义,这将为我提供一些启发。但是,在我发现的某些资源中使用的语言令人生畏-例如在Wikipedia上。有没有人有一个真实的例子可以向我展示这个主题(也许是C#)的观点?


3
当务之急是去一家餐厅订购6盎司的盎司。牛排(煮熟的稀有食物),薯条(带有番茄酱),侧沙拉(带有牧场)和可乐(没有冰)。服务员提供的正是他所要的东西,他的收费是14.50美元。另一方面, Declarative去了一家餐馆,告诉服务员他只想花12美元左右吃顿晚餐,他很想吃牛排。服务生以6盎司重回。牛排(煮熟的介质),土豆泥,蒸的西兰花,晚餐卷和一杯水。他的费用为$ 11.99。
cs_pupil

Answers:


786

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

使用命令式编程,您可以逐步告诉编译器您要发生的事情。

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

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);

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

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


8
+1。但是,您首先提到LINQ,但是与此相关的示例有哪些?
Zano

15
在哪里使用声明性LINQ扩展方法。它不使用C#语言功能,而是使用声明性API。我不想在这里混合消息,这就是为什么我避免在声明方法的基础上增加语言的原因。
Reed Copsey

241
在我看来,声明式编程不过是抽象层。
Drazen Bjelovuk

8
这是一个很好的答案,但它正在回答不纯函数式和命令式编程之间的差异。collection.Where不使用Linq提供的声明性语法- 有关示例,请参阅msdn.microsoft.com/zh-cn/library/bb397906.aspxfrom item in collection where item%2 != 0 select item这将是声明性形式。仅仅因为该函数在System.Linq命名空间中,调用函数就不会成为声明式编程。
皮特·柯坎

30
@PeteKirkham所使用的语法不是问题-声明式与命令式的关系更多地是在于声明要发生的事情与确切地说明它是如何发生的。使用集成语法或扩展方法语法是一个单独的问题。
Reed Copsey 2015年

145

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

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个内核进行快速并行搜索的最佳时机。 ,作为一个程序员,不关心任何这些条件,你不必编写代码来处理这样的任何特殊情况。


30
该Python示例不是声明性的。
Juanjo Conti,2009年

18
@Juanjo:它IS decalarative。
missingfaktor 2010年

3
这里的第一个陈述比第二个陈述更具声明性吗?
泽纳2012年

17
同意Juanjo和zenna-循环构造在重构为较短的表示法时不会神奇地转换为声明性程序。
菲利克斯·弗兰克

11
与@FelixFrank不同,并倾向于@missingfaktor的“大胆”声明。传统的“完全”声明方式是filter(lambda x: x < 5, range(20)),这只是将其重构为较短符号的另一种方式。这与列表理解表达式(具有清晰的“地图”和“过滤器”部分)没有任何有意义的区别,列表理解表达式的创建(请参阅pep 202)其明确意图是创建一个更简洁的符号。在这种情况下,这种列表理解将更加清晰/习惯。
yoniLavi 2015年

100

声明式与命令式

一个编程范式是计算机编程的基本风格。主要有四个范例:命令式,声明式,功能式(被视为声明式范例的子集)和面向对象。

声明式编程:是一种编程范例,用于表达计算的逻辑(做什么)而没有描述其控制流程(如何做)。声明性领域特定语言(DSL)的一些著名示例包括CSS,正则表达式和SQL子集(例如SELECT查询)。许多标记语言(例如HTML,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 odd"
var evenNumbers = numbersOneThroughTen.Select(number => number % 2 == 0);

这两个示例都得出相同的结果,并且一个不比另一个好或坏。第一个示例需要更多代码,但是代码是可测试的,命令式方法使您可以完全控制实现细节。在第二个示例中,代码可以说更具可读性。但是,LINQ无法让您控制幕后发生的事情。您必须相信LINQ将提供所请求的结果。


7
您可以添加面向对象的描述吗?
mbigras


54

以上所有答案和其他在线帖子均提及以下内容:

  • 使用声明式编程,您可以编写描述所需内容的代码,而不必描述如何获取它
  • 与命令式编程相比,您应该更喜欢声明式编程

他们还没有告诉我们如何实现这一目标。为了使程序的一部分更具说明性,其他部分必须提供抽象以隐藏实现细节(这是命令性代码)。

  • 例如,LINQ比循环(for,while等)更具声明性,例如,您可以list.Where()用来获取新的过滤列表。为此,Microsoft完成了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();

PS声明式编程的极限是发明新的领域特定语言(DSL):

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

有说明性程序设计的许多很好的例子:阵营CloudFormationTerraform
engineforce

因此,“声明式”编程仅意味着将完成任务的代码移至某个函数?
Guillaume F.

@GuillaumeF。它与创建特定于域的抽象有关。例如, -银行:应创建功能,如debitdeposit等代替重复imparative码account.balance += depositAmount
engineforce

44

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

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

在命令的最后,您可以使用DirectX或OpenGL非常命令地绘制按钮,复选框等...逐行(或者实际上是逐个三角形)。由您决定如何绘制用户界面。

在声明性的一端,您拥有WPF。您基本上编写了一些XML(是的,从技术上来说是“ XAML”),并且框架为您完成了工作。您说出用户界面的外观。由系统确定如何执行此操作。

无论如何,这是另一件事要考虑。仅仅因为一种语言是声明性或命令性语言并不意味着它不具有另一种语言的某些功能。

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

这一切的要旨:

声明式-> what您想完成

势在必行-> how您要完成它


27

我喜欢剑桥课程及其示例的解释:

  • 声明式 -指定要做什么而不要怎么
    • 例如:HTML描述应该在网页上显示的内容,而不是应该在屏幕上显示的内容
  • 当务之急 -指定内容方式
    • int x; -什么(说明性)
    • x=x+1; - 怎么样

“不应该如何在屏幕上绘制” ......这是否意味着CSS势在必行呢?
Chef_Code

11
不。它也可以视为声明性的,因为您只需说出自己想要的内容- “使此单元格边框为蓝色”即可。想象一下,您想用命令式方法绘制相同的边框(例如:JavaScript)。然后,您需要说“去指向(x1,y1),在此点和(x2,y1)之间画一条蓝线,从(x2,y1)到(x2,y2)画一条蓝线,然后画一条蓝线从(x2,y2)到(x1,y2),从(x1,y2)到(x1,y1)画一条蓝线
ROMANIA_engineer 16'Mar

@ROMANIA_engineer,请问在哪里可以找到这种剑桥课程?
测试团队

@testteam,在Google上搜索以下“ cl.cam.ac.uk教学ooprog”。您可以从URL更改年份。
ROMANIA_engineer

@ROMANIA_engineer,知道了,谢谢
测试团队

26

差异主要与抽象的整体级别有关。使用声明式,在某些时候,您与各个步骤相距甚远,因此程序在如何获得结果方面具有很大的自由度。


您可以将每条指令视为连续的某个地方:

抽象度:

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

声明式真实世界示例:

  1. 图书管理员,请检查一下Moby Dick的副本。(馆员可以自行选择执行请求的最佳方法)

命令式真实世界示例:

  1. 进入图书馆
  2. 查找图书组织系统(卡片目录-旧学校)
  3. 研究如何使用卡目录(您也忘记了,对)
  4. 弄清楚货架的标签和组织方式。
  5. 弄清楚书架上书籍的组织方式。
  6. 通过组织系统从卡片目录中交叉引用书籍位置以找到所述书籍。
  7. 带书到结帐系统。
  8. 签出书。

这不只是关于抽象,而不仅仅是声明性/命令性吗?您仍在指导图书馆员携带这本书。
kamathln

更新后的答案更加完整,并在解决方案中包括了这一方面。
朗讯·福克斯

3
这有一些道理,但这不是一个完整的定义。通过声明式编程,您可以说最终目标,而无需考虑起点。对于命令式编程,定义的起点很重要。这就像给出地址与给出指示的区别。无论您身在何处,地址都是有用的。如果您从其他地方开始,则指示无效。
Cthutu

24

命令式编程要求开发人员逐步定义如何执行代码。要以命令式的方式发出指示,您可以说:“去第一街,左转到Main,行驶两个街区,右转到Maple,然后在左侧的第三座房屋停靠。” 声明性版本听起来可能像这样:“开车去苏的家。” 一个人说如何做某事;对方说需要做什么。

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

  • 它不会强迫旅行者记住很长的说明。
  • 它允许旅行者在可能的情况下优化路线。

Calvert,C库尔卡尼,D(2009)。基本LINQ。艾迪生·韦斯利(Addison Wesley)。48。


11

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

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}
     )

(来自维基百科Linq的示例)


2
您有错别字:linq语句是声明性的,而不是命令性的(您拥有“ C#功能更需要命令性,如Linq”应为声明性。)
Reed Copsey

已修正(前段时间)
McKay

8

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

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

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

一个很好的比较是ASP.Net“代码隐藏”模型。您具有声明性的“ .ASPX”文件,然后具有命令性的“ ASPX.CS”代码文件。我经常发现,只要我能在脚本的声明性一半中做所有我想做的事情,就会有更多的人可以跟随所做的事情。


7

菲利普·罗伯茨那里偷窃:

  • 命令式编程告诉机器如何做某事(导致您想要发生的事情)
  • 声明式编程告诉机器您要发生的事情(计算机会找出如何做)

两个例子:

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

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


5
与对该问题的许多令人恐惧的答案一样,您的“声明式”编程示例就是函数式编程示例。“ map”的语义是“将此功能按顺序应用于数组的元素”。您不允许在运行时按执行顺序留出任何余地。
皮特·柯坎

4

命令式编程
一种要求编程规则的编程语言,例如C / C ++,Java,COBOL,FORTRAN,Perl和JavaScript。以此类语言编写的程序员必须基于对数据处理和编程的了解,制定适当的操作顺序才能解决问题。

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

声明性编程示例为CSS,HTML,XML,XSLT,RegX。


2

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

优点:与直接指定某些命令性算法的变体相比,仅以某种硬编码(和检查)格式指定数据更简单且更不易出错。有些复杂的规范只能以某种DSL形式直接编写。DSL数据结构中使用的最佳和频率是集和表。因为您没有元素/行之间的依赖关系。并且当您没有依赖项时,您可以自由修改和简化支持。(例如,将模块与类进行比较-与您满意的模块以及与具有脆弱基类问题的类进行比较),声明性和DSL的所有优点都立即受益于该数据结构(表和集合)的好处。另一个优点-如果DSL或多或少是抽象的(设计良好),则可以更改声明性语言vm的实现。例如,进行并行执行。

缺点:你猜对了。通用(由DSL参数化)命令式算法/ vm实现可能比特定的算法慢和/或占用大量内存。在某些情况下。如果这种情况很少见-那就别管它了,让它变慢。如果频率很高-在这种情况下,您始终可以扩展DSL / vm。放慢所有其他情况的速度,确定...

PS框架介于DSL和命令式命令之间。作为所有中途解决方案……它们结合了缺陷而不是利益。他们不是那么安全,也不是那么快:)看一看所有行业的haskell-它介于强大的简单ML和灵活的metaprog Prolog之间,这是什么怪物。您可以将Prolog视为具有仅布尔函数/谓词的Haskell。以及针对Haskell的灵活性有多简单...


2

我只是想知道为什么没有人提到Attribute类作为C#中的声明性编程工具。该页面的热门回答刚刚谈到了LINQ作为声明性编程工具。

根据维基百科

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

因此,LINQ作为一种功能语法,绝对是一种声明性方法,但是C#中的属性类(作为配置工具)也是声明性的。这是阅读更多内容的一个很好的起点:C#属性编程快速概述


这是一个很好的观点,而且是一个更明显的例子.linq之类的东西看起来仍然很必要,因此对于不知道区别的人来说是一个困惑,但是除了声明性之外很难看到其他任何属性。 “标记”成员以声明您要对成员执行的操作,但您并不是在说任何方法。形式的逻辑虽然不那么繁重,但是具有属性,您根本没有描述任何逻辑。您只是在标记事物
user441521,2016年

2

只是在移动应用程序开发方面添加了另一个示例。在iOS和Android中,我们有Interface Builders,可以在其中定义应用程序的UI。

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

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

同样,一些新语言(例如Angular JS)也专注于声明式设计UI,我们可能会看到许多其他语言提供相同的支持。像Java一样,没有任何好的声明方式可以在Java swing或Java FX中绘制本机桌面应用程序,但是在不久的将来,它们可能会。


Java不是BTW的首字母缩写
Mordechai

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);
}

本质上,声明性知识会跳过某些元素,从而这些元素上形成抽象层。声明式编程也是如此。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.