声明式与命令式编程

声明式和命令式

In computer science, declarative programming is a programming paradigm — a style of building the structure and elements of computer programs—that expresses the logic of a computation without describing its control flow.
This is in contrast with imperative programming, which implements algorithms in explicit steps.

声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做(how)。

In computer science, imperative programming is a programming paradigm that uses statements that change a program's state. In much the same way that the imperative mood in natural languages expresses commands, an imperative program consists of commands for the computer to perform. Imperative programming focuses on describing how a program operates.
The term is often used in contrast to declarative programming, which focuses on what the program should accomplish without specifying how the program should achieve the result.

命令式编程:命令“机器”如何去做事情(how),这样不管你想要的是什么(what),它都会按照你的命令实现。

声明式编程和命令式编程的代码例子

举个简单的例子,假设我们想让一个数组里的数值翻倍。

我们用命令式编程风格实现,像下面这样:

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]

我们直接遍历整个数组,取出每个元素,乘以二,然后把翻倍后的值放入新数组,每次都要操作这个双倍数组,直到计算完所有元素。

而使用声明式编程方法,我们可以用 Array.map 函数,像下面这样:

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

map 利用当前的数组创建了一个新数组,新数组里的每个元素都是经过了传入 map 的函数处理。

map 函数所作的事情是将直接遍历整个数组的过程归纳抽离出来,让我们专注于描述我们想要的是什么(what)。注意,我们传入 map 的是一个纯函数;它不具有任何副作用(不会改变外部状态),它只是接收一个数字,返回乘以二后的值。

在一些具有函数式编程特征的语言里,对于集合数据类型,还有一些其他常用的声明式的函数方法。例如 reduce、filter

声明式编程很奇怪吗?

作为程序员,我们非常习惯去指出事情应该如何运行。当我们已经知道了如何告诉机器该如何做事时(假如集合数据类型没有 reduce、filter、map 等函数,那么作为程序员,肯定想到的是 for 循环,变量操作等等),为什么我们需要去学习这种看起来有些怪异的归纳抽离出来的函数工具?

很多情况下,命令式编程很好用。当我们写业务逻辑,我们通常必须要写命令式代码,没有可能在我们的专项业务里也存在一个可以归纳抽离的实现。但是我们可以在小的功能组件或者 UI 渲染等层面做声明式编程。比如 SwiftUI、Flutter、React、Vue 等。

但是,如果我们花时间去学习(或发现)声明式的可以归纳抽离的部分,它们能为我们的编程带来巨大的便捷。首先,我可以少写代码,这就是通往成功的捷径。而且它们能让我们站在更高的层面是思考,站在顶层去思考我们想要的是什么,而不是站在最底层思考事情该如何去做。

声明式编程语言:SQL

没意识到我们在学习数据库的时候,SQL 就是声明式编程。
你可以把 SQL 当做一个处理数据的声明式查询语言。完全用SQL写一个应用程序?这不可能。但如果是处理相互关联的数据集,它就显的无比强大了。

像下面这样的查询语句:

SELECT * from dogs INNER JOIN owners WHERE dogs.owner_id = owners.id;

如果我们用命令式编程方式实现这段逻辑:

//dogs = [{name: 'Fido', owner_id: 1}, {...}, ... ]
//owners = [{id: 1, name: 'Bob'}, {...}, ...]
 
var dogsWithOwners = []
var dog, owner
 
for(var di=0; di < dogs.length; di++) {
 
  dog = dogs[di]
 
  for(var oi=0; oi < owners.length; oi++) {
 
    owner = owners[oi]
    if (owner && dog.owner_id == owner.id) {
 
      dogsWithOwners.push({
        dog: dog,
        owner: owner
 
      })
    }
  }}
}

我可没说SQL是一种很容易懂的语言,也没说一眼就能把它们看明白,但基本上还是很整洁的。

SQL代码不仅很短,不不仅容易读懂,它还有更大的优势。因为我们归纳抽离了 how,我们就可以专注于 what,让数据库来实现 how。

而SQL例子里我们可以让数据库来处理how,来替我们去找我们想要的数据。如果需要用到索引(假设我们建了索引),数据库知道如何使用索引,这样性能又有了大的提升。如果在此不久之前它执行过相同的查询,它也许会从缓存里立即找到。通过放手how,让机器来做这些有难度的事,我们不需要掌握数据库原理就能轻松的完成任务。

声明式编程:d3.js

一个能体现出声明式编程的真正强大之处地方是用户界面、图形、动画编程。

开发用户界面是有难度的事。因为有用户交互,我们希望能创建漂亮的动态用户交互方式,通常我们会用到大量的状态声明和很多相同作用的代码,这些代码实际上是可以归纳提炼出来的。

d3.js 里面一个非常好的声明时归纳提炼的例子就是它的一个工具包,能够帮助我们使用 JavaScript 和 SVG 来开发交互和动画型的数据可视化模型。

下面是一个例子。这是一个d3可视化实现,它为data数组里的每个对象画一个圆。

//var data = [{x: 5, y: 10}, {x: 20, y: 5}]
 
var circles = svg.selectAll('circle')
 
                    .data(data)
 
circles.enter().append('circle')
 
           .attr('cx', function(d) { return d.x })

           .attr('cy', function(d) { return d.y })
 
           .attr('r', 0)
           .transition().duration(500).attr('r', 5)

从头再看一遍代码,想一想,我们是在声明我们想要的图案是什么样子,还是在说如何作图。你会发现这里根本没有关于how的代码。我们只是在一个相当高的层面描述我们想要的是什么:

我要画圆,圆心在data数据里,当增加新圆时,用动画表示半径的增加。
太酷了,我们没有写任何循环,这里没有状态管理。画图操作通常是很难写,很麻烦,很让人讨厌,但这里,d3归纳提取了一些常用的操作,让我们专注于描述我们想要的是什么。

现在再看,d3.js 很容易理解吗?不是,它绝对需要你花一段时间去学习。而学习的过程基本上需要你放弃去指明如何做事的习惯,而去学会如何描述我想要的是什么。

最初,这可能是很困难的事,但经过一些时间的学习后,一些神奇的事情发生了——你变得非常非常有效率了。通过归纳提取how,d3.js 能让你真正的专注说明你想要看到的是什么,让你在一个个更高的层面解决问题,解放你的创作力。

声明式编程的总结

  • 声明式编程让我们去描述我们想要的是什么,让底层的软件、计算机去解决如何实现。

  • 在很多情况中,就像我们看到的一样,声明式编程能给我们的编程带来真正的提升,通过站在更高层面写代码,我们可以更多的专注于 what,而这正是我们开发软件真正的目标。

问题是,程序员习惯了去描述 how,这让我们感觉很好很安心能够控制程序的运行状态,不放走任何我们不能看见不能理解的处理过程。

有时候这种紧盯着 how 不放的做法是没问题的。如果我需要对代码进行更高性能的优化,我需要对 what 进行更深一步的描述来指导 how。有时候对于某个业务逻辑没有任何可以归纳提取的通用实现,我们只能写命令式编程代码。

但大多数时候,我们可以、而且应该寻求声明式的写代码方式,如果没有发现现成的归纳提取好的实现,我们应该自己去创建。起初这会很难。但就像我们使用 SQL 和 D3.js, 我们会长期从中获得巨大的回报!

最近苹果开发者大会推出的 SwiftUI 和流行的 React、Vue 等都是声明式编程方式。和传统的 Dom 技术相比,声明式真的是开发效率的大幅提升(可以看看 Vue.js 官网的宣传视频)

你可能感兴趣的:(声明式与命令式编程)