我一直在搜索关于声明式和命令式编程的定义,希望能让我更加明白。然而,我发现一些资源中使用的语言让我感到有些困难 - 比如在维基百科上。 请问是否有人可以给我展示一个真实世界的例子,以便更好地理解这个主题(也许是用C#)?
我一直在搜索关于声明式和命令式编程的定义,希望能让我更加明白。然而,我发现一些资源中使用的语言让我感到有些困难 - 比如在维基百科上。 请问是否有人可以给我展示一个真实世界的例子,以便更好地理解这个主题(也许是用C#)?
一个很好的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);
}
这里我们说的是:
然而,在声明式编程中,您编写描述您想要的内容的代码,但不一定需要说明如何实现(声明所需的结果,而不是逐步说明):
var results = collection.Where( num => num % 2 != 0);
在这里,我们所说的是“给我们所有奇数的东西”,而不是“遍历集合。检查这个项目,如果它是奇数,则将其添加到结果集合中。”
在许多情况下,代码将是两种设计的混合体,因此并不总是非黑即白。
collection.Where
没有使用Linq提供的声明性语法 - 参见https://msdn.microsoft.com/en-us/library/bb397906.aspx中的示例,`from item in collection where item%2 != 0 select item` 是声明式形式。仅仅因为调用的函数在System.Linq命名空间中,并不能将其变成声明式编程。 - Pete Kirkham声明式编程是指你说明 想要什么,而命令式语言则是说明 如何 获得你想要的结果。
下面是 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也可以这样说。SELECT score FROM games WHERE id < 100;
SQL的“编译器”可以对这个查询进行“优化”,因为它知道id
是一个索引字段——或者可能它不是索引字段,在这种情况下,它将不得不遍历整个数据集。或者也可能SQL引擎知道现在正是利用全部8个核心进行快速并行搜索的最佳时机。你作为一个程序员,并不需要关心这些条件,也不需要编写处理任何特殊情况的代码。
filter(lambda x: x < 5, range(20))
,只是另一种缩写形式。从任何有意义的角度来看,这与列表推导表达式(具有明确的“map”和“filter”部分)没有什么不同,而该列表推导表达式是为了创建更简洁的表示法而创建的(请参见 pep 202)。在这种情况下,使用列表推导表达式会更清晰/符合惯用语。 - yoniLavi声明式编程 vs. 命令式编程
编程范式是计算机编程的基本风格。有四种主要的编程范式:命令式、声明式、函数式(被认为是声明式范式的子集)和面向对象。
声明式编程:是一种表达计算逻辑(做什么)而不描述其控制流程(如何做)的编程范式。一些著名的声明式领域特定语言(DSLs)包括CSS,正则表达式和SQL的子集(例如SELECT查询)。许多标记语言,如HTML、MXML、XAML、XSLT等,通常也是声明式的。声明式编程试图模糊程序作为指令集和程序作为所需答案断言之间的区别。
命令式编程:是一种以改变程序状态的语句来描述计算的编程范式。命令式程序可以被双重地视为编程命令或数学断言。
功能编程:是一种将计算视为数学函数评估并避免状态和可变数据的编程范式。它强调应用函数,与强调状态更改的命令式编程风格形成对比。 在纯函数语言(例如Haskell)中,所有函数都没有副作用,并且状态更改仅表示为转换状态的函数。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将提供所请求的结果。
以下是网络帖子和本文提到的:
他们没有告诉我们如何实现。为了使程序的一部分更具声明性,其他部分必须提供抽象以隐藏实现细节(即命令式代码)。
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();
P.S. 声明式编程的极致是发明新的领域特定语言(DSL):
account.balance += depositAmount
。那么,声明式编程风格只是为了倡导在正确的抽象层次上创建良好的API吗? - Nevermore我再举一个在声明式/命令式编程讨论中很少出现的例子: 用户界面!
在C#中,您可以使用各种技术构建UI。
在命令式端,您可以使用DirectX或OpenGL以非常命令式的方式逐行(或实际上是三角形)画出按钮、复选框等。绘制用户界面的方式由您决定。
在声明式端,您有WPF。你基本上编写一些XML(是的,是“XAML”技术),框架会为您完成工作。您说出用户界面的外观,系统会找出如何完成它。
无论如何,这只是另一个需要考虑的问题。仅仅因为一种语言是声明式还是命令式,并不意味着它没有另一种语言的某些特性。
此外,声明式编程的好处之一是,目的通常更容易从读取代码中理解,而命令式编程则可以更精细地控制执行。
总的来说:
声明式->你想要做什么
命令式->你想要怎样做
这主要与抽象层次有关。在声明式编程中,你离具体步骤越远,程序在得到结果时就有更多的自由度。
你可以将每个指令视为处于一个连续性范围的某个位置:
抽象度:
Declarative <<=====|==================>> Imperative
声明式现实世界示例:
命令式现实世界示例:
我喜欢剑桥课程上的一个解释和他们的例子:
int x;
- 什么(声明式)x=x+1;
- 如何CSS
是命令式的呢? - Chef_Code以下是两个例子:
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
注意命令式示例涉及创建一个新变量、改变它并返回该新值(即如何使某事发生),而声明式示例执行给定的输入并根据初始输入返回新值(即我们想要发生什么)。
命令式编程是直接告诉计算机要做什么以及如何执行,比如指定顺序等。
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的示例)