函数式编程(FP)

发表于 2年以前  | 总阅读数:265 次

写在前面

可能大家都听过武侠小说中的内功招式,商业大佬讲的,一些唱歌选秀评委口中的感情技巧

那程序员的江湖里是不是也存在没有感情的API 调用工程师。随着前端生态的迅速发展,目前框架、语法、提案都更新换代的很快。各种各样的招式层出不穷,让人应接不暇,身心俱乏,对于内力的领悟和沉淀已经迫在眉睫,因为不管框架 API 怎么变一些编程的内在思想是不会变的。

js 为了实现面向对象的思想,做了很多事情,导致大家在学习 js 的时候,会遇到复杂的原型、原型链、继承,还有对人不友好的 this ;而当我们用这些东西组合起来模拟面向对象的特性的时候,就更加痛苦了。但我们可以使用一种更友好的方式,函数式编程。

什么是函数式编程

函数式编程(functional programing)是编程范式之一。我们常见的范式还有面向过程、面向行为、面向对象等。

范式:我们可以认为它是一种思维模式加上它的实现方法,简单说就是编程的方法论。

  • 面向过程编程:简单解释就是按照步骤来实现。

  • 面向行为编程:它是函数式编程的衍生范型,将电脑运算平展为一系列的变化,并且避免使用程序指令以及堆叠的对象。

  • 面向对象编程:它的思维方式是把现实世界中的事物抽象成程序世界中的类和对象,然后通过封装,继承和多态来演示事物之间的联系。

  • 面向函数式编程:它的思维方式是把现实世界中的事物和事物之间的联系,抽象到程序世界中。

    函数式编程特点:

  • 程序的本质:就是利用计算机的计算能力将输入转化成对应的输出

  • 函数式编程中的函数指的不是编程语言里的函数,而是数学意义上的映射关系。比如 y=sin(x) 中 x 和 y 值的映射关系。

  • 纯函数:相同的输入获得相同的输出(无副作用)。

  • 函数式编程就是对 数据(函数) 映射关系的抽象。举个例子:

比如我们已知 a,b 两个直角边,求斜边长度。

y = \sqrt{a^{2}+b^{2}}

//非函数式 y = (a^2 + b^2)^0.5
const a = 3;
const b = 4;
const y = Math.sqrt(a*a + b*b)

//函数式 y = f(a, b)
const f = (a, b) => {
 return Math.sqrt(a * a + b * b)
}
const y = f(3, 4)

通过代码实现,我们可以看出函数式就是对过程变形关系的抽象。抽象的是处理过程,然后我们只需关注输入和输出。接下来我们看一下几种函数式编程应用。

高阶函数 (high-order-function)

“一个以函数作为参数或返回的函数。高阶函数,它虽然听起来很复杂,但其实并不难。并且非常的实用。要完全理解这个概念,首先必须了解 头等函数(https://developer.mozilla.org/zh-CN/docs/Glossary/First-class_Function)(First-Class Functions)的概念。

头等函数简单的讲就是函数也是一个对象,它能赋值给变量,能作为参数返回

而高阶函数就是以函数为参数或返回的函数

// 一个批量处理数组元素的例子
const use = ( arr, fn ) => {
  return arr.map( i => fn(i))
}

闭包 (closure)

“函数和其周围词法环境的引用捆绑在一起形成闭包。闭包的概念并不复杂,只是定义比较绕。举一段代码的

function once(fn){
  let done = false;
  return function (){
    if(!done){
      done = true;
      fn.apply(this, arguments)
    }
  }
}

const logOnce = once(console.log)

//此时只会执行一次
logOnce(1)
logOnce(1)

闭包的本质是函数在执行时,会被放到执行栈上去执行,执行结束后被移除,但是堆上作用域成员由于外部的引用而不能被释放。因此内部函数依然可以访问外部函数的成员。

可能有的同学会问,为什么有引用不会被释放?这是因为 js 的 垃圾回收 (http://www.ruanyifeng.com/blog/2017/04/memory-leak.html)机制中最常用的是标记清除和引用计数。这里我们就不展开,有兴趣的同学可以自行了解一下。

纯函数 (pure function)

“相同的输入会得到相同的输出,而且没有任何可观测的副作用。举一个数组中纯函数和不纯函数的

let numberArr = [1,2,3,4,5]
//纯函数
numberArr.slice(0,2) //[1,2]
numberArr.slice(0,2) //[1,2]
//不纯函数
numberArr.splice(0,2) //[1,2]
numberArr.splice(0,2) //[3,4]

函数式编程不会保留计算中间的结果,所以变量是不可变、无状态的。我们可以把一个函数的执行结果交给另一个函数去处理。有的时候我们会拆分很多细粒度的函数库,这里可以了解一下 lodash (https://lodash.com/docs/4.17.15)功能库,它提供了丰富的对数组、数字、对象、字符串、函数等操作的方法。

纯函数的好处:

  • 对于耗时的操作,可对执行结果缓存,提高代码性能。
  • 方便测试,降低排查问题的难度。
  • 在多线程环境下(web worker),可对共享内存数据任意执行。

柯里化 (currying)

假设一个场景,我们需要写一个函数来判断一个人的年龄是否大于 18 岁。你可能会直接写:

const lucy = {age: 17}
const bob = {age: 100}

function checkAge(age){
  return age > 18
}

checkAge(lucy.age)
checkAge(bob.age)

这样没什么问题,但是我们如果要更改基准值的时候判断是否大于 20,那可能又需要重新定义一个 checkAge20 的新函数了。如果这个基准一直在变... 有的同学立马就想到了,那我传入基准值就好啦。

const lucy = {age: 17}
const bob = {age: 100}

function checkAge(min, age){
  return age > min
}

checkAge(18, lucy.age)
checkAge(18, bob.age)

这样 checkAge 没什么问题,但是发现我们每次都需要输入重复的基准值。有没有什么办法可以避免重复呢?让我们试试使用闭包和高阶函数:

function checkAge(min){
  return function(age){ // 函数作为返回
    return age > min; // 闭包,引用外部参数
  }
}

// 如果用 es6 的语法会更简洁
const checkAge = min => age => age > min

const checkAge18 = checkAge(18)

checkAge18(lucy.age)

其实我们改造到这里就是函数的柯里化。那什么是柯里化呢?

当函数有多个参数的时候,我们可以对函数进行改造,只接收部分参数,然后返回一个函数继续等待接收剩余参数,并且返回相应的结果。

lodash 中的 FP

在lodash的官网上,我们很容易找到一个 function program guide 。在 lodash / fp 模块中提供了实用的对函数式编程友好的方法。里面的方式有以下的特性:

  • 不可变
  • 已柯里化(auto-curried)
  • 迭代前置(iteratee-first)
  • 数据后置(data-last) 假如我们有一个需求是将空格字符串以小写“ - ”分割该如何实现呢?

例如:(CAN YOU FEEL MY WORLD --> can-you-feel-my-world)

import _ from 'lodash'

const str = "CAN YOU FEEL MY WORLD"
const split = _.curry((sep, str)=>_.split(str, sep))
const join = _.curry((sep, arr)=>_.join(arr, sep))
const map = _.curry((fn, arr)=>_.map(arr, fn))

const f = _.flow(split(' '),map(_.toLower), join('-'))

f(str) //'can-you-feel-my-world'

我们在使用 lodash 时,做能很多额外的转化动作,那我们试试 fp 模块吧。

import fp from 'lodash/fp'

const str = "CAN YOU FEEL MY WORLD"
const f = fp.flow(fp.split(' '),fp.map(fp.toLower), fp.join('-'))

f(str) //'can-you-feel-my-world'

这种编程方式我们称之为 PointFree,它有 3 个特点:

  • 不需要指明处理的数据
  • 只需要合成运算过程
  • 需要定义一些辅助的基本运算函数 当然使用的时候还是需要注意一下参数的描述。官网上有一个 是这样的:
// The `lodash/map` iteratee receives three arguments:
// (value, index|key, collection)
_.map(['6', '8', '10'], parseInt);
// ➜ [6, NaN, 2]

// The `lodash/fp/map` iteratee is capped at one argument:
// (value)
fp.map(parseInt)(['6', '8', '10']);
// ➜ [6, 8, 10]

FP 中的 map 方法和 lodash 中的 map 方法参数的个数是不同的。

什么是函数组合

弄明白了柯里化,我们开始函数组合了。

开发过程中,有的同学使用高阶函数高阶组件的时候很容易写出洋葱代码。

withRouter(Form.create()(connect(({ model }) => ({ status: model.status}))(Index)))

这段代码通常我们会使用装饰器(decorator)的方案优化掉。

withRouter
@Form.create()
@connect(({ model }) => ({
  status: model.status,
}))
Index

但是装饰器只适用于组件 Component,对于拥抱 hooks 的函数组件并不适用。

在 redux 和 lodash 都有函数组合的方法提供,分别是 compose 和 flow,fn = compose(f1,f2,f3),他可以帮助我们将上面的洋葱代码改造成管道的形式。我们需要注意管道的执行顺序,默认都是从右到左执行。compose 的实现也是特别的简单的。

function compose(...args){
  return function(value){
    return args.reverse().reduce(function(acc,fn){
      return fn(acc)
    }, value)
  }
}
// es6
const compose = (...args) => value => args.reverse().reduce((acc,fn) => fn(acc), value)

对于函数组合,我们也可以随时插入一些用来调试的函数。

const log = curry((label, x) => { console.log(label, x); return x; });
const y = compose(c, log('after a'),b, log('after a'), a);

函子(Functor)

到目前来说,我们已经了解了一定的函数式编程的基础,但是我们还没有演示在函数式编程中如何把副作用控制在可控范围内、异常处理、异步操作等。在处理副作用之前,先聊下函子。

什么是函子?

容器:包容值和值的变形关系(这个变形关系就是函数)。

函子:一个特殊的容器,通过一个普通的对象来实现,该对象具有 map 方法, map 方法可以运行一个函数对值进行处理(变形关系)。

下面我们通过一段代码来看一下:

class Container{
  constructor(value){
    //私有的值 不对外公布
    this._value = value
  }
  //接收一个处理值的函数
  map(fn){// map 是一个契约名称 fn 需要是一个纯函数
    //返回一个新的函子
    return new Container(fn(this._value))
  }
}

new Container(1)
  .map(x => x + 1)
  .map(x => x * x)

这样我们可以通过创建时给定初始值,map 方法来修改这个值。但是一直使用 new 关键字,让代码看起来很面向对象,让我们来改造一下。

class Container{
  //of 的作用就是给我们返回一个函子对象,我们把 new 关键字封装在里面
  static of(value){
    return new Container(value)
  }

  constructor(value){
    this._value = value
  }

  map(fn){
    return new Container(fn(this._value))
  }
}
new Container.of(1)
  .map(x => x + 1)
  .map(x => x * x)

但是这样的一个基础的函子还是存在许多的问题,比如初始化的值与操作的方法不匹配、异常处理、可控副作用、异步执行等。因此衍生出一系列的函子来解决这些问题,这里罗列一下对应的函子和它们解决的问题:

  • maybe 函子: 空值问题
  • Either 函子:异常处理
  • IO 函子:副作用处理
  • Task 函子:异步执行
  • Monad 函子:IO 函子多层嵌套

主流框架、库中的应用

在 Redux 中,要写一个中间件代码大致是这样的:

const middleware = store => next => action => {
  // 具体实现
};

其实对于最后的实现主体来说无非都是拿到storenextaction三个参数而已。完全可以用下面的方式定义:

(store, next, action) => {...}

但是作者 Dan Abramov 还是采用了更具有函数式特性的方式去定义。

另外,React 16.8 版本开始正式的支持了 hooks。hooks 对比类组件的写法有几处优势这也刚好是符合函数式编程的特性的。

  • 通过自定义 hooks 来共享一些组件的逻辑,如果用类组件实现,只能通过高阶组件模拟,这样会不断嵌套,无用的“龟壳”。
  • 每个方法都是独立的, 不需要像类组件那样在一个 mount 生命周期里做一堆不相关的操作,更新时又做一堆不相关的操作。不相关的逻辑整合在一个生命周期内,本来就是不易读、不易维护的。
class Example extends Component {
  componentDidMount() {
    //注册事件
    //请求Api
    //设置状态 等等
  }
  componentWillUnmount(){
    //取消一些监听事件
  }
}

而 Hooks(主要是 useEffect)取代了生命周期的概念,让代码的依赖逻辑更接近本质。函数式编程为组件的编写提供了更高的灵活度与可读性

总结

函数式编程是一种范式、一种思想、一种约定。他有着一定的优势,更高的可组合性,灵活性以及容错性。但是在实际应用中是很难用函数式去表达的,我们应该将其当做我们现有储备的一种补充,而并非最优解去看待。以往的开发过程,我们可能习惯了用变量存储和追踪程序的状态,不停的在一些节点打印语句来观察程序的过程,现代的 JavaScript 库已经开始尝试拥抱函数式编程的概念以获取这些优势来降低系统复杂度。统一存储管理数据,将程序的运行状态置于可预见状态里。React、Rxjs、Redux 等 js 库都是这一理念的最佳实践者。

参考

  • 函数式编程的早期历史 (https://zhuanlan.zhihu.com/p/24648375?refer=marisa)
  • lodash -- FP Guide (https://github.com/lodash/lodash/wiki/FP-Guide)
  • 函数式编程初探 (http://www.ruanyifeng.com/blog/2012/04/functional_programming.html)
  • 函数式编程(五)—— 函子 (https://segmentfault.com/a/1190000023744960)

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/ttQy_v-_QBKjcz2LPZvfNQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237271次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8108次阅读
 目录