关于C#:声明式编程和命令式编程有什么区别?

What is the difference between declarative and imperative programming?

我一直在搜索网络,寻找一个声明式和命令式编程的定义,这将为我提供一些帮助。然而,在我发现的一些资源中使用的语言是令人望而生畏的——例如在维基百科。是否有人有一个现实世界的例子,他们可以向我展示,可能会给这个主题带来一些观点(可能是在C中)?


声明式编程和命令式编程的一个很好的例子是LINQ。

使用命令式编程,您一步一步地告诉编译器您想要发生什么。

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

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

使用命令式编程,我们将逐步完成这个过程,并决定我们想要什么:

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

这里,我们说:

  • 创建结果集合
  • 单步执行集合中的每个编号
  • 检查数字,如果是奇数,将其添加到结果中
  • 另一方面,通过声明性编程,您可以编写描述您想要什么的代码,但不一定是如何获得它(声明您想要的结果,但不是一步一步地声明):

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

    在这里,我们说的是"把所有奇怪的东西都给我们",而不是"跨过收藏。选中此项,如果它是奇数,请将其添加到结果集合中。"

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


    声明性编程是指你说出你想要的,命令式语言是指你说出如何得到你想要的。

    Python中的一个简单示例:

    1
    2
    3
    4
    5
    6
    7
    8
    # 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)

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

    通常,使用LINQ会产生一种声明性风格,因为您没有说如何获得想要的;您只说您想要的。关于SQL,你也可以这么说。

    声明性编程的一个好处是,它允许编译器做出可能导致比手工编写更好的代码的决定。如果您有类似

    1
    SELECT score FROM games WHERE id < 100;

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


    陈述式与命令式

    程序设计范式是计算机程序设计的一种基本形式。有四个主要的范例:命令式、声明式、功能性(被认为是声明式范例的一个子集)和面向对象。

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

    命令式编程:是用改变程序状态的语句描述计算的编程范式。声明性程序可以被双重地视为编程命令或数学断言。

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

    下面的命令式编程示例在msdn中,循环遍历数字1到10,并找到偶数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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将提供请求的结果。


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

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

    在命令的最后,您可以使用DirectX或OpenGL非常有必要地绘制按钮、复选框等。一行接一行(或真的,三角形接三角形)。由您决定如何绘制用户界面。

    在声明性的末尾,您有WPF。您基本上编写了一些XML(是的,是的,技术上是"xaml"),框架为您工作。你说用户界面是什么样子的。如何做到这一点取决于系统。

    不管怎样,这只是另一个需要考虑的问题。仅仅因为一种语言是声明性的或命令性的,并不意味着它没有另一种语言的某些特征。

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

    其要点是:

    声明性->what您要完成

    命令->how你要完成它


    以上所有答案和其他在线帖子都提到了以下内容:

    • 使用声明性编程,可以编写描述所需内容的代码,但不一定是如何获得它的代码。
    • 与命令式编程相比,您应该更喜欢声明式编程

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

    • 例如,linq比循环(for、while等)更具声明性,例如,可以使用list.where()获取新的筛选列表。为了实现这一点,微软已经完成了Linq抽象背后的所有繁重工作。

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

    在任何程序中,您都将同时拥有命令性代码和声明性代码,您的目标是将所有命令性代码隐藏在抽象后面,以便程序的其他部分可以声明性地使用它们。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // JavaScript example

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


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

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


    Imperative programming requires developers to define step by step how
    code should be executed. To give directions in an imperative fashion,
    you say,"Go to 1st Street, turn left onto Main, drive two blocks,
    turn right onto Maple, and stop at the third house on the left." The
    declarative version might sound something like this:"Drive to Sue’s
    house." One says how to do something; the other says what needs to be
    done.

    The declarative style has two advantages over the imperative style:

    • It does not force the traveler to memorize a long set of instructions.
    • It allows the traveler to optimize the route when possible.

    Calvert,C Kulkarni,D(2009年)。基本Linq。艾迪生·韦斯利。48。


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

    • 声明性-指定要做什么,而不是如何做
      • 例如:HTML描述了网页上应该出现的内容,而不是应该如何在屏幕上绘制。
    • 命令-指定内容和方式
      • int x;—什么(声明性)
      • x=x+1;如何


    这种差异主要与总体抽象级别有关。在声明性的情况下,在某一点上,您离单个步骤太远了,以至于程序对于如何获得结果有很大的自由度。

    你可以把每一条指令都看成是连续的:

    抽象程度:

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

    声明性现实世界示例:

  • 图书管理员,请给我看一本《白鲸记》。(图书管理员根据自己的判断选择执行请求的最佳方法)
  • 必要的现实世界示例:

  • 进入图书馆
  • 查找图书组织系统(卡片目录-旧学校)
  • 研究如何使用卡片目录(你也忘了,对吧)
  • 了解货架的标签和组织方式。
  • 弄清楚书架上的书是如何整理的。
  • 从卡片目录与组织系统交叉引用图书位置,找到所述图书。
  • 带书到结账系统。
  • 结账。

  • 命令式编程就是明确地告诉计算机要做什么以及如何做,比如指定顺序等等。

    C:

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

    声明性是指告诉计算机要做什么,但实际上不是如何做。在这方面,datalog/prolog是人们首先想到的语言。基本上一切都是声明性的。你不能真正保证订单。

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

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

    同样的事情也可能是强制性的:

    1
    2
    3
    4
    5
    6
    7
    8
    dynamic foo = SomeCollection.Where
         (
              c => c.SomeProperty < (SomeValue * 2)
         )
         .Select
         (
              c => new {c.SomeProperty, c.OtherProperty}
         )

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


    In computer science, declarative programming is a programming paradigm that expresses the logic of a computation without describing its control flow.

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

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

    比较好的是ASP.NET"代码隐藏"模型。您有声明性的".aspx"文件,然后是命令式的"aspx.cs"代码文件。我经常发现,如果我能在脚本的声明性部分中完成我所需要的全部工作,那么更多的人可以跟踪正在完成的工作。


    从菲利普·罗伯茨这里偷东西:

    • 命令式编程告诉机器如何做一些事情(导致您想要发生的事情)
    • 声明性编程告诉机器您希望发生什么(并且计算机知道如何做)

    两个例子:

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

    强制性地:

    1
    2
    3
    4
    5
    6
    7
    8
    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]

    Declaratively:

    1
    2
    3
    4
    5
    6
    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。汇总列表中的所有项

    强制性地

    1
    2
    3
    4
    5
    6
    7
    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

    声明地

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

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

    注意命令式示例如何涉及创建一个新的变量,对其进行变异,并返回该新值(即,如何使某些事情发生),而声明性示例则在给定的输入上执行,并基于初始输入返回新值(即,我们希望发生的事情)。


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

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

    声明性编程示例有css、html、xml、xslt、regx。


    声明性程序只是它的一些或多或少的"通用"命令式实现/VM的数据。

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

    缺点:你猜对了。通用(并由DSL参数化)命令式算法/VM实现可能比特定算法慢和/或内存不足。在某些情况下。如果这种情况很少见,那就别想了,慢慢来。如果它是频繁的-你总是可以为这种情况扩展你的DSL/VM。在某个地方减缓了所有其他情况,当然…

    P.S.框架是DSL和命令之间的中间部分。就像所有的半途而废的解决方案…他们结合了挑战,而不是利益。他们不那么安全,也不那么快:)看看所有行业的杰克哈斯克尔-它介于强大的简单ML和灵活的metaprog prolog之间…真是个怪物。您可以将prolog视为具有仅布尔函数/谓词的haskell。它对抗哈斯克尔的灵活性有多简单…


    我只是想知道为什么没有人提到属性类作为C中的声明性编程工具。本页的热门答案刚刚提到Linq作为声明性编程工具。

    根据维基百科

    Common declarative languages include those of database query languages
    (e.g., SQL, XQuery), regular expressions, logic programming,
    functional programming, and configuration management systems.

    因此,LINQ作为一种函数语法,绝对是一种声明性方法,但是C中的属性类作为一种配置工具,也是声明性的。这里有一个很好的起点来阅读更多关于它的内容:C属性编程的快速概述


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

    使用这些构建器绘制的UI本质上是声明性的,在这里我们拖放组件。实际的排水是在框架和系统的下面进行的。

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

    另外,一些新的语言,比如AngularJS,正专注于声明性地设计UI,我们可能会看到许多其他语言提供相同的支持。像Java在Java Swing或Java FX中没有任何好的声明性方法来绘制本地桌面应用程序,但在不久的将来它们可能会。


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

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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
    "
    , x, y, x == y);
    }

    本质上,声明性知识跳过某些元素,在这些元素上形成一个抽象层。声明性编程也做同样的事情。