函数式编程

前言

现在主流的两种方式,一种是 OOP (Object-oriented programming ) 面向对象编程,另一种是 FP (Functional Programming)。本文稍微普及一下 FP 这种方式。

特点

与其他编程范式相比,FP的主要区别在于声明性方法(FP)与命令式方法。在我们深入了解正式定义之前,让我们通过查看示例来探索差异。

?声明式写法
// 数组每个元素乘三
const triple = (arr) => {
 let results = []
 for (let i = 0; i < arr.length; i++){
   results.push(arr[i] * 3)
 }
 return results
}

// 数组求和
const sum = (arr) => {
 let result = 0
 for (let i = 0; i < arr.length; i++){
   result += arr[i]
 }
 return result
}
复制代码
  • 这段代码片段的主要复杂性源于这样一个事实:我们不是告诉计算机我们想要它做某些事而是指示如何做到这一点
  • 而且可读性很差(???), 这是一个简单示例,但随着程序的增长和功能变得更加复杂,使用像这样的for循环会创建非常简单的代码,并且需要我们的大脑分析循环的内部工作,同时跟踪索引,变量和更多。命令式代码在阅读时会增加认知负荷,并且随着时间的推移会使推理和逻辑更容易出错。
命令式写法
// 第一个例子
const  triple  =(arr)=>  arr.map((currentItem)=> currentItem *  3)

//第二个例子
const  sum  =(arr)=>  arr.redeuce((prev,current)=> prev + current,0)
复制代码
  • 首先,我保证在给定相同输入的情况下,这两种方法每次都会产生相同的输出
  • 很明显,声明性片段比命令式片段更简,它也更容易阅读。在我们的例子中是一个匿名函数,它告诉程序我希望它对数组中的每个元素做什么,而不是指示程序我希望它访问哪些索引等等

函数作为一等公民

所谓"第一等公民"(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。

// 比如我们要先请求然数据然后渲染模板,可能会这么写
const render = (json) => {}
httpGet('/post/2', json => render(json))
复制代码

如果这个时候我们除了拿数据还要拿 error 错误信息怎么办,你可能会想再加个参数

const render = (json, err) => {}
httpGet('/post/2', (json, err) => render(json, err))
复制代码

但是以后如果可还有其他参数怎么办??如果用一等公民的写法这样

const render = (json, err)  => {}
httpGet('/post/2', render)
复制代码

纯函数

纯的意思是引用透明没有任何副作用 更多概念性的东西可以参考阮一峰大大的入门教程

// 不纯的
const minimum = 21
const checkAge = age  => {
 return age  >= minimum
}
// 纯的
const checkAge = age  => {
 const minimum = 21
 return age  >= minimum
}
复制代码

再看下引用类型:

const data = [1, 2, 3]
const a = data.slice(0, 1)
// [1]
const b = data.slice(0, 1)
// [1]
const c = data.slice(0, 1)
// 此处是为了对比所以用了const,正常肯定会报错的
// [1]
const a = data.splice(0, 1)
// [1]
const b = data.splice(0, 1)
// [2]
const c = data.splice(0, 1)
复制代码

柯里化 Curry

函数柯⾥里里化就是只传递给函数⼀一部分参数来调⽤用它,让它返回⼀一个函数去处 理理剩下的参数

举个例子

function add (x, y) {
  return x + y
}
function add (x) {
  return function (y) {
return x + y }
}
const add = x => y => x + y
// add(2,3) 等价于 add(2)(3)
//舒服了
复制代码

这里我们定义了一个 add 函数,它接受一个参数并返回一个新的函数。调用 add 之后,返回的函数就通过闭包的方式记住了 add 的第一个参数。一次性地调用它实在是有点繁琐,好在我们可以使用三方函数式编程库一个特殊的 curry 帮助函数使这类函数的定义和调用更加容易,后面我会把函数式编程库不错的列出来

代码组合 Compose

const b = f(a)
const c = g(b)

不用函数式:const c = g(f(a))
用了函数式:const c = compose(g, f)(a)

吃我个?

const data = [1, 2, 3]
const head = arr  => arr[0]
const reverse = arr  =>
  arr.reduce((acc, item)  => [item].concat(acc), [])
const last = compose(
  head,
reverse
)
last(data) // 3
 
复制代码

这里只是举个例子取数组最后的一项,真正写起来不会这么麻烦那么我接下来举个大家用的比较多对象判空的例子

const user = {
    name: 'cai',
    address: {
        city: 'hangzhou'
    }
}
const city =
 user &&
 user.address  &&
 user.address.city
 // 或者这样
 const city = !user
? undefined
: !user.address
? undefined
: user.address.city
复制代码

用了函数式以后

const prop = key  => obj  => (obj   === undefined  || obj   === null)
  ? null
: obj[key]
const getUserCity = compose(
  prop('city'),
  prop('address')
)
getUserCity(user)  // hangzhou
复制代码

用对象的形式

class Maybe {
  constructor(val) {
    this. __val = val
  }
  static of(val) {
    return new Maybe(val)
  }
  isNothing() {
    return (this. __val   === null  || this. __val   === undefined)
  }
  map(f) {
    return (
      this.isNothing()
        ? new Maybe(null)
        : new Maybe(f(this. __val))
) }
}
/* 调用 */
// props :: s  -> {s: a}  -> a
const prop = key  => obj  => obj[key]
Maybe
  .of(user)
  .map(prop('address'))
  .map(prop('city'))
 // Maybe {  __val: 'hangzhou' }
复制代码

题外话

用过 react 童鞋值到 react 有 hoc (高阶组件)的概念 其实也是把组件当成参数的形式传进来返回一个新的组件这点是共通的

推荐

  • ramda、lodash/fp :函数式编程库
  • Recompose:React 的 Lodash
  • RxJS: 用来处理理事件的 lodash

QA

个人理解系的不好望指证

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值