声明式编程和命令式编程的比较(转)

作者: Philip Roberts 原文链接

Imperative-vs-Declarative

英文原文:Imperative vs Declarative

先统一一下概念,我们有两种编程方式:命令式和声明式。

我们可以像下面这样定义它们之间的不同:

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

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

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

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

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的函数(这里是function (n) { return n*2 })的处理。

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

在一些具有函数式编程特征的语言里,对于 list 数据类型的操作,还有一些其他常用的声明式的函数方法。例如,求一个list里所有值的和,命令式编程会这样做:

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

而在声明式编程方式里,我们使用reduce函数:

var numbers = [1,2,3,4,5]
var total = numbers.reduce (function (sum, n) {
  return sum + n
});
console.log (total) //=> 15

reduce函数利用传入的函数把一个list运算成一个值。它以这个函数为参数,数组里的每个元素都要经过它的处理。每一次调用,第一个参数(这里是sum)都是这个函数处理前一个值时返回的结果,而第二个参数(n)就是当前元素。这样下来,每此处理的新元素都会合计到sum中,最终我们得到的是整个数组的和。

同样,reduce函数归纳抽离了我们如何遍历数组和状态管理部分的实现,提供给我们一个通用的方式来把一个list合并成一个值。我们需要做的只是指明我们想要的是什么

声明式编程很奇怪吗?

如果你之前没有听说过mapreduce函数,你的第一感觉,我相信,就会是这样。作为程序员,我们非常习惯去指出事情应该如何运行。“去遍历这个list”,“if 这种情况 then 那样做”,“把这个新值赋给这个变量”。当我们已经知道了如何告诉机器该如何做事时,为什么我们需要去学习这种看起来有些怪异的归纳抽离出来的函数工具?

在很多情况中,命令式编程很好用。当我们写业务逻辑,我们通常必须要写命令式代码,没有可能在我们的专项业务里也存在一个可以归纳抽离的实现。

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

声明式编程语言: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。

我们的命令式编程代码会运行的很慢,因为需要遍历所有list里的每个狗的主人。

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

声明式编程:d3.js

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

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

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

第一次(或第5次,甚至第10 =次)你开发d3程序时可能会头大。跟SQL一样,d3是一种可视化数据操作的强大通用工具,它能提供你所有how方法,让你只需要说出你想要什么。

下面是一个例子(我建议你看一下这个演示)。这是一个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) 

没有必要完全理解这段代码都干了什么(你需要一段时间去领会),但关键点是:

首先我们收集了svg里所有的圆,然后把data数组数据绑定到对象里。

D3对每个圆都绑定了那些点数据有一个关系表。最初我们只有两个点,没有圆,我们使用.enter()方法获取数据点。这里,我们的意图是画一个圆,中心是xy,初始值是0 ,半秒后变换成半径为5

为什么我说这很有意思?

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

我要画圆,圆心在 data 数据里,当增加新圆时,用动画表示半径的增加。

这太神奇了,我们没有写任何循环,这里没有状态管理。画图操作通常是很难写,很麻烦,很让人讨厌,但这里,d3归纳提取了一些常用的操作,让我们专注于描述我们想要的是什么

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

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

声明式编程的总结

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

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

问题是,程序员习惯了去描述how,这让我们感觉很好很舒服——强力——能够控制事情的发生发展,不放走任何我们不能看见不能理解的处理过程。

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

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

非常感谢 @srbaker, @maniacyak@jcoglan 对这篇文章的的建议和补充。

中文原文出处:(https://kb.cnblogs.com/page/181030/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容