2022,值得学习系列之:函数式编程

来源: 央广网
2023-08-14 20:35:05

央广网08月14日:分享心得嫩草含羞草未满十八免费(每上一个楼梯就撞一下腰)..._《果冻传媒新剧国产第一集》2022年排行榜-果冻影视网ど“大陆的发展是全体中国人民,包括台湾同胞的最大机遇。”全国政协常委、著名经济学家林毅夫在论坛大会上发表演讲时表示。aernCXotkP-VSHDYS7123FID-hYoqFQ0hP

 

在诞生近 70 年之后, 函数式编程(Functional Programming)开始获得越来越多的关注。并且随着时间推移、技术演变,它与前端开发,结合的也越发紧密;前些日子,读了些文章,对函数式编程有了粗浅的了解,并在代码上做了些实践,发现这种思想非常有趣,喜之不已。近日我女神对它突感好奇,于是乎,便结合业界已有之分享,结合自己理解与实践,梳理成文,以便解释。

函数式编程

备注:原文首发于:浅谈关于「函数式编程」的理解 | 宜想悠然亭,@2022 年 05 月 30 日。

与非函数式的区别

非函数式的示例代码如下:

1
2
3
4
let counter = 0
const increment = () => {
counter++
}

1
2
3
const increment = (counter) => {
return counter + 1
}

你可能会觉得这个例子太过普通。但,这就是函数式编程的准则:不依赖于外部的数据,而且也不改变外部数据的值,而是返回一个新的值给你

再看一段复杂一点,却较为 实用的代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const handleSearch = (_keywords) => {
let queryResultArr;
if (!_keywords) {
queryResultArr = []
} else {
queryResultArr = getQueryResult(_keywords)
}
setQueryResultArr(queryResultArr)
setIsShowResults(queryResultArr.length > 0)
}
const throttle = (fn, wait) => {
let pre = Date.now();
return function () {
const context = this;
const args = arguments;
const now = Date.now();
if (now - pre >= wait) {
fn.apply(context, args)
pre = Date.now()
}
}
}
const requestSearchFunc = throttle(handleSearch, 200)
const handleInputChange = (event) => {
const value = event.target.value;
setKeywords(value)
requestSearchFunc(value)
}

可以使用 throttle节流函数,如:requestSearchFunc;这个技术其实就是 Currying 技术(把一个函数的多个参数分解成多个函数, 然后把函数多层封装起来,每层函数都返回一个函数去接收下一个参数这样,可以简化函数的多个参数)。从这个技术上,你可能体会到函数式编程的理念:把函数当成变量来用,关注于描述问题而不是怎么实现,这样可以让代码更易读。

过程式 vs 函数式

过程式编程,主要要采取过程调用,或函数调用的方式来进行流程控制;它主要关注:一步一步地解决问题。这是完全有效的编码方式,但当您希望应用程序扩展时,它存在许多缺点。而函数式编程( Functional Programming)关注的是:描述要做什么,而不是如何做(describe what to do, rather than how to do it)。

举例来说,对于一个英文名数组,需要将其中短横线命名,转化为大驼峰格式。基于传统过程式编程,可能你不会想太多,直接将想法用代码来表达出来,临时变量、循环语句,用的飞起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const nameArr = ['nice-jade', 'rayso-lee', 'jon-snow', 'arya-stark'];
const newArr = [];
for (let i = 0, len = nameArr.length; i < len ; i++) {
let name = nameArr[i];
let tempNameArr = name.split('-');
let newNameArr = [];
for (let j = 0, nameLen = tempNameArr.length; j < nameLen; j++) {
let newFormatName = tempNameArr[j][0].toUpperCase() + tempNameArr[j].slice(1);
newNameArr.push(newFormatName);
}
newArr.push(newNameArr.join(' '));
}
console.log(newArr)
// [ 'Nice Jade', 'Rayso Lee', 'Jon Snow', 'Arya Stark' ]

这是可以达成目标的答案,它完全面向过程;虽然在编码时候,能够将想法淋漓尽致表现出来。但对于阅读的人,十分不够友好。因为这中间夹杂了复杂的逻辑,充斥了大量临时变量、循环、状态变化等等;通常您需要:从头读到尾才知道它具体做了什么,而且一旦出问题,很难定位。当然,你也可以将如上代码,拆分成几个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const nameArr = ['nice-jade', 'rayso-lee', 'jon-snow', 'arya-stark']
const capitalize = (str) => str[0].toUpperCase() + str.slice(1).toLowerCase()
const convertName = (name) => {
let tempNameArr = name.split('-')
let newNameArr = []
for (let j = 0, nameLen = tempNameArr.length; j < nameLen; j++) {
let newFormatName = capitalize(tempNameArr[j])
newNameArr.push(newFormatName)
}
return newNameArr.join(' ')
}
const getNewArr = (nameArr) => {
const newArr = []
for (let i = 0, len = nameArr.length; i < len; i++) {
let name = nameArr[i]
const newName = convertName(name)
newArr.push(newName)
}
return newArr
}
const newNameArr = getNewArr(nameArr)
console.log(newNameArr)
// [ 'Nice Jade', 'Rayso Lee', 'Jon Snow', 'Arya Stark' ]

如此一来,阅读代码时,需要考虑的上下文少了许多,也就更容易。不像第一个示例,如果没有合理的注释说明,你还需要花些时间来理解。而把代码逻辑封装成了函数后,就相当于:给每个相对独立的程序逻辑取了个名字,于是代码成了自解释的。幸好,在这份代码中,函数间调用,没有依赖共享的变量,否则将会更加复杂。但,仍是充斥了临时变量、循环,增加代码量的同时,也加大了理解之难度。

如果基于函数式编程思想,那会是怎样的代码呢?

1
2
3
4
5
6
7
8
9
const { compose, map, join, split } = require('ramda')
const nameArr = ['nice-jade', 'rayso-lee', 'jon-snow', 'arya-stark']
const capitalize = (str) => str[0].toUpperCase() + str.slice(1).toLowerCase()
const convertName = compose(join(' '), map(capitalize), split('-'))
const newNameArr = nameArr.map((item) => convertName(item))
console.log(newNameArr)
// [ 'Nice Jade', 'Rayso Lee', 'Jon Snow', 'Arya Stark' ]

如上代码(有借助 ramda ——「实用的函数式 JavaScript 工具库」来实现),虽依然把程序的逻辑分成了函数,不过这些函数都是 Functional 的。因为它们有三个症状:

  1. 它们之间没有共享的变量。
  2. 函数间通过参数和返回值来传递数据。
  3. 在函数里没有临时变量。

从这个编程思路,可以清晰看出,函数式编程的思维过程是完全不同的,它的着眼点是函数,而不是过程,它强调的是:通过函数的组合、变换去解决问题,而不是通过写什么样的语句去解决问题;当你的代码越来越多的时候,这种函数的拆分和组合,就会产生出更加强大的力量。

什么函数式编程?

函数式编程 ,或称函数程序设计、泛函编程(英语:Functional Programming),是一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。比起指令式编程,函数式编程更加强调程序执行的结果,而非执行的过程;倡导利用若干简单的执行单元,让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。

在函数式编程中,函数是头等对象,意思是说一个函数,既可以作为其它函数的输入参数值,也可以从函数中返回值,被修改或者被分配给一个变量。

不仅最古老的函数式语言 Lisp 重获青春,而且新的函数式语言层出不穷,比如 Erlang、clojure、Scala、F#等等。目前最当红的 Python、Ruby、Javascript,对函数式编程的支持都很强,就连老牌的面向对象的 Java、面向过程的 PHP,都忙不迭地加入对匿名函数的支持。越来越多的迹象表明,函数式编程已经不再是学术界的最爱,开始大踏步地在业界投入实用。

也许继”面向对象编程”之后,”函数式编程”会成为下一个编程的主流范式(paradigm)。未来的程序员恐怕或多或少都必须懂一点。—— 函数式编程初探 @阮一峰 (2012 年)

函数,即是一种描述集合和集合之间的转换关系,输入通过函数,都会返回有且只有一个输出值。函数实际上是一个关系,或者说成一种映射,而这种映射关系是可 组合 的,当知道一个函数的输出类型,可以匹配另一个函数的输入,那他们就可以进行组合。如上述代码中提及的 convertName 函数。

在编程世界中,需要处理的逻辑,其实只有“数据”和“关系”,而关系就是 函数。一旦映射关系(函数)找到了,问题即能迎刃而解;剩下的事情,就是让数据通过这种关系,然后转换成另一个数据而已。

函数式编程的特点

函数是”第一等公民”

所谓 “第一等公民” (First Class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。如上文中代码:

1
const convertName = compose(join(' '), map(capitalize), split('-'))

无状态和数据不可变

无状态(Statelessness)和数据不可变(Immutable data),这是函数式编程的核心概念:

  • 数据不可变:它要求你所有的数据都是不可变的,这意味着如果你想修改一个对象,那你应该创建一个新的对象用来修改,而不是修改已有的对象。
  • 无状态:主要是强调对于一个函数,不管你何时运行,它都应该像第一次运行一样,给定相同的输入,给出相同的输出,完全不依赖外部状态的变化。

为了实现这个目标,函数式编程提出函数应该具备的特性:没有副作用和纯函数。

没有”副作用”

所谓”副作用”(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。

函数式编程强调没有”副作用”,意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。

引用透明

引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或”状态”,只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。

有了前面的第三点和第四点,这点是很显然的。其他类型的语言,函数的返回值往往与系统状态有关,不同的状态之下,返回值是不一样的。这就叫”引用不透明”,很不利于观察和理解程序的行为。

惰性执行

所谓惰性执行(Lazy Evaluation),指的是函数只在需要的时候执行,即:不产生无意义的中间变量。像上面👆的例子,函数式编程跟命令式编程最大的区别就在于:几乎没有中间变量,它从头到尾都在写函数,只有在最后的时候才通过调用 convertName 产生实际的结果。

尾递归优化

迭代在函数式语言中常用递归来完成,用递归来实现控制流程的机制,是函数式编程的一个非常重要的特点。我想您当然知道 递归 的害处,那就是如果递归很深的话,stack 受不了,并会导致性能大幅度下降。所以,我们使用尾递归优化技术——每次递归时都会重用 stack,这样一来能够提升性能。尾递归的实现,其实是基于编译器的识别和优化的,编译器发现一个函数是尾递归,就会把它实现为与命令式编程中的迭代差不多的汇编码。

函数式编程相关技术

  • map & reduce :这个技术不用多说了,函数式编程最常见的技术,就是对一个集合做 Map 和 Reduce 操作。这比起过程式的语言来说,在代码上要更容易阅读。(传统过程式的语言需要使用 for/while 循环,然后在各种变量中把数据倒过来倒过去的)这个很像 C++中的 STL 中的 foreach,find_if,count_if 之流的函数的玩法。
  • pipeline:这个技术的意思是,把函数实例成一个一个的 action,然后,把一组 action 放到一个数组或是列表中,然后把数据传给这个 action list,数据就像一个 pipeline 一样顺序地被各个函数所操作,最终得到我们想要的结果。
  • compose:它可以接收多个独立的函数作为参数,然后将这些函数进行组合串联,最终返回一个“组合函数”。pipecompose 的共同点是:都返回“组合函数”,区别则是执行的顺序不同,前者是从左向右执行,后者则是从右向左执行。
  • recursing 递归 :递归最大的好处就简化代码,他可以把一个复杂的问题用很简单的代码描述出来。注意:递归的精髓是描述问题,而这正是函数式编程的精髓。
  • currying:把一个函数的多个参数分解成多个函数, 然后把函数多层封装起来,每层函数都返回一个函数去接收下一个参数这样,可以简化函数的多个参数。在 C++中,这个很像 STL 中的 bind_1st 或是 bind2nd。
  • higher order function 高阶函数:所谓高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出,就像面向对象对象满天飞一样。

函数式编程的意义

  • 代码简洁,开发快速:大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快;
  • 接近自然语言,易于理解:函数式编程的自由度很高,可以写出很接近自然语言的代码;
  • 更方便的代码管理:不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同;
  • 更少的出错概率:因为每个函数都很小,而且相同输入,永远可以得到相同的输出,测试也很简单;
  • 易于”并发编程”:因为它不修改变量,所以根本不存在”锁”线程的问题;
  • 代码的热升级:基于没有副作用特点,只要保证接口不变,内部实现是外部无关的。

函数式编程,结语

正如您在函数式编程中看到的,它希望使用小的(理想情况下是纯函数)函数来解决问题。这种方法也非常具有可 扩展性,并且函数可以 重用

没有更好和更坏的范式。有经验的开发人员,可以看到每种范式的优点,并为给定的问题选择相对更合适的。过程式编程,并不是说你不能使用函数;函数式编程也不会阻止你使用“类”。这些范式,只是以一种随代码增长而有益的方式,来帮助解决问题。

所参考系列文章

猜您可能感兴趣的文章

(另)【ling】(一)【yi】(方)【fang】(面)【mian】(,)【,】(理)【li】(财)【cai】(产)【chan】(品)【pin】(的)【de】(收)【shou】(益)【yi】(下)【xia】(滑)【hua】(导)【dao】(致)【zhi】(了)【le】(购)【gou】(买)【mai】(理)【li】(财)【cai】(行)【xing】(为)【wei】(的)【de】(减)【jian】(少)【shao】(。)【。】(一)【yi】(是)【shi】(近)【jin】(年)【nian】(来)【lai】(我)【wo】(国)【guo】(的)【de】(利)【li】(率)【lv】(市)【shi】(场)【chang】(不)【bu】(断)【duan】(下)【xia】(行)【xing】(,)【,】(以)【yi】(7)【7】(天)【tian】(逆)【ni】(回)【hui】(购)【gou】(利)【li】(率)【lv】(为)【wei】(例)【li】(,)【,】(今)【jin】(年)【nian】(6)【6】(月)【yue】(1)【1】(3)【3】(日)【ri】(,)【,】(中)【zhong】(国)【guo】(人)【ren】(民)【min】(银)【yin】(行)【xing】(再)【zai】(度)【du】(下)【xia】(调)【tiao】(1)【1】(0)【0】(b)【b】(p)【p】(到)【dao】(了)【le】(1)【1】(.)【.】(9)【9】(0)【0】(%)【%】(,)【,】(跌)【die】(破)【po】(2)【2】(%)【%】(大)【da】(关)【guan】(。)【。】(随)【sui】(着)【zhe】(利)【li】(率)【lv】(不)【bu】(断)【duan】(走)【zou】(低)【di】(,)【,】(理)【li】(财)【cai】(的)【de】(投)【tou】(资)【zi】(收)【shou】(益)【yi】(率)【lv】(预)【yu】(期)【qi】(也)【ye】(随)【sui】(之)【zhi】(下)【xia】(降)【jiang】(。)【。】(二)【er】(是)【shi】(资)【zi】(管)【guan】(新)【xin】(规)【gui】(之)【zhi】(后)【hou】(,)【,】(打)【da】(破)【po】(刚)【gang】(兑)【dui】(,)【,】(非)【fei】(标)【biao】(压)【ya】(降)【jiang】(,)【,】(理)【li】(财)【cai】(产)【chan】(品)【pin】(投)【tou】(资)【zi】(风)【feng】(险)【xian】(上)【shang】(升)【sheng】(。)【。】

 【】●▂●●0●极乐宝鉴完整版在线

  fengjiangdegegechanghe(huaming)shiqunian7yuebeitongcunderenpiandaomiandianmiaowadiqude。changheshoupiandeyuanyintongyangshi“gaoxingongzuo”,hetayiqibeipianguoqudehaiyou3ren。tamenzainanninghuihe,youyunnanzoushanlu,touduzhimiandian。その週の木曜日にc僕は永沢さんと食堂で顔をあわせた。彼は食事をのせた盆を持って僕のとなりに座りcこのあいだいろいろ済まなかったなと謝まった。2004年,李春生调往河南省公安厅出任政治部主任。

  赛伦生物在年报中表示,因公司产品主要应用于在户外和野外作业或活动中受伤的患者人群,报告期内受外部环境影响,社会经济活动减少、建设建筑工程停滞、居民外出活动减少等,产品市场受到直接影响。◎tadezhangfuhejiaren,douzhidaotashiyigepianzile。

 (7)【7】(月)【yue】(2)【2】(3)【3】(日)【ri】(,)【,】(黑)【hei】(龙)【long】(江)【jiang】(齐)【qi】(齐)【qi】(哈)【ha】(尔)【er】(市)【shi】(第)【di】(三)【san】(十)【shi】(四)【si】(中)【zhong】(学)【xue】(体)【ti】(育)【yu】(馆)【guan】(发)【fa】(生)【sheng】(坍)【tan】(塌)【ta】(。)【。】(据)【ju】(新)【xin】(京)【jing】(报)【bao】(援)【yuan】(引)【yin】(央)【yang】(视)【shi】(新)【xin】(闻)【wen】(消)【xiao】(息)【xi】(,)【,】(7)【7】(月)【yue】(2)【2】(4)【4】(日)【ri】(上)【shang】(午)【wu】(1)【1】(0)【0】(时)【shi】(,)【,】(记)【ji】(者)【zhe】(从)【cong】(齐)【qi】(齐)【qi】(哈)【ha】(尔)【er】(市)【shi】(第)【di】(三)【san】(十)【shi】(四)【si】(中)【zhong】(学)【xue】(校)【xiao】(体)【ti】(育)【yu】(馆)【guan】(楼)【lou】(顶)【ding】(坍)【tan】(塌)【ta】(事)【shi】(故)【gu】(救)【jiu】(援)【yuan】(指)【zhi】(挥)【hui】(部)【bu】(获)【huo】(悉)【xi】(,)【,】(最)【zui】(后)【hou】(一)【yi】(名)【ming】(被)【bei】(困)【kun】(学)【xue】(生)【sheng】(已)【yi】(搜)【sou】(救)【jiu】(到)【dao】(,)【,】(已)【yi】(无)【wu】(生)【sheng】(命)【ming】(体)【ti】(征)【zheng】(。)【。】(此)【ci】(次)【ci】(事)【shi】(故)【gu】(共)【gong】(造)【zao】(成)【cheng】(1)【1】(1)【1】(人)【ren】(死)【si】(亡)【wang】(,)【,】(事)【shi】(故)【gu】(调)【tiao】(查)【zha】(工)【gong】(作)【zuo】(正)【zheng】(在)【zai】(全)【quan】(面)【mian】(推)【tui】(进)【jin】(中)【zhong】(。)【。】「大丈夫ですよcたぶん」  changanjiezhishizhuyidao,ribenyibuyiquzhuisuimeiguozhengfudetongshi,meiguodexinpianjutouquezaijitiduibaigongshuo“bu”。

    据报道,美国国务院原本公布的日程显示,布林肯和秦刚将在北京时间18日下午7时30分共进工作晚餐。法新社称,19日,中共中央政治局委员、中央外办主任王毅预计将会见布林肯,后者还将与美国交换生和商界领袖举行座谈。「気をつけるよ」(2)【2】(0)【0】(1)【1】(8)【8】(年)【nian】(,)【,】(雅)【ya】(戈)【ge】(尔)【er】(又)【you】(对)【dui】(中)【zhong】(信)【xin】(股)【gu】(份)【fen】(()【(】(0)【0】(2)【2】(6)【6】(7)【7】(.)【.】(H)【H】(K)【K】())【)】(下)【xia】(手)【shou】(。)【。】(其)【qi】(在)【zai】(一)【yi】(季)【ji】(度)【du】(通)【tong】(过)【guo】(精)【jing】(准)【zhun】(增)【zeng】(持)【chi】(将)【jiang】(对)【dui】(中)【zhong】(信)【xin】(股)【gu】(份)【fen】(的)【de】(持)【chi】(股)【gu】(比)【bi】(例)【li】(由)【you】(4)【4】(.)【.】(9)【9】(9)【9】(%)【%】(提)【ti】(升)【sheng】(至)【zhi】(5)【5】(%)【%】(,)【,】(并)【bing】(顺)【shun】(利)【li】(将)【jiang】(中)【zhong】(信)【xin】(股)【gu】(份)【fen】(从)【cong】(可)【ke】(供)【gong】(出)【chu】(售)【shou】(的)【de】(金)【jin】(融)【rong】(资)【zi】(产)【chan】(调)【tiao】(整)【zheng】(至)【zhi】(长)【chang】(期)【qi】(股)【gu】(权)【quan】(投)【tou】(资)【zi】(栏)【lan】(目)【mu】(下)【xia】(,)【,】(由)【you】(此)【ci】(产)【chan】(生)【sheng】(的)【de】(净)【jing】(资)【zi】(产)【chan】(可)【ke】(辨)【bian】(认)【ren】(公)【gong】(允)【yun】(价)【jia】(值)【zhi】(与)【yu】(账)【zhang】(面)【mian】(价)【jia】(值)【zhi】(的)【de】(差)【cha】(额)【e】(高)【gao】(达)【da】(9)【9】(3)【3】(.)【.】(0)【0】(2)【2】(亿)【yi】(元)【yuan】(。)【。】

  彼は足を机の上にのせたままビールを飲みcあくびをした。zhiyaoyoushijian,lifangjiuxihuandaoyixianqu。zhexienian,tadezujibianjiquanguoshijigeshengfenchumushengchanhenongyezhijiaoyixian。zuoweishandongmuyuanerjijiaoshou,tadelinianshi:zhiyejiaoyuyidingyaoweiquyujingjifazhanfuwu。zhexienian,talvduiyuzhongduolongtouqiyeduijie,jinxingzhenduixingpeixun,shengchanwentichuxianzainali,tadejishuzhidaobiangenjindaonali。

  xuyaozhuyideshi,xunikaheshitikashiliangzhangka,jinebuhutong。「それが上野駅の思い出話」  根据美国人口普查局2021年发布的数据,非洲裔占美国人口总数的13.2%,却占贫困人口总数的23.8%;拉丁裔占美国人口总数的18.7%,却占贫困人口总数的28.1%。

(来源:范长江)

发布于:阳谷县
声明:该文观点仅代表作者本人,搜狐号系信息发布平台,搜狐仅提供信息存储空间服务。
用户反馈 合作

Copyright © 2023 Sohu All Rights Reserved

搜狐公司 版权所有