Skip to content

Latest commit

 

History

History
194 lines (135 loc) · 5.02 KB

2018-11-17__函数式编程.md

File metadata and controls

194 lines (135 loc) · 5.02 KB

函数式编程简介

什么是函数式编程

简单说,函数式编程(FP)是一种编程范式,比如我们熟知的面向对象编程,也是一种编程方式,和指令式编程不同,它的主要思想是把运算过程尽量写成一系列嵌套的函数调用。

特点

  1. 函数是第一公民 第一公民的意思是说函数和其他数据类型比如整型、字符串等地位相同,可以把他存放到数组中、当做参数传递、作为函数返回值、赋值给变量等等。

  2. 单元测试。 在函数式编程中,可以简单认为一切都是const,函数不会有副作用,不会修改运行时的任何东西,决定函数的执行结果的唯一因素就是参数,这意味着可以非常容易的进行单元测试

  3. 易于调试 因为FP不会修改runtime,出现bug时,这些bug是100%必现的,不同于指令式编程,一个bug可能有时重现,有时无法重现,这是因为函数依赖一些外部数据,而这些外部数据又在某些情况下被修改,从而导致这个问题,而在FP中,这种情况不会存在

  4. 并发执行 不需要任何改动,所有FP程序都是可以并发执行的。由于根本不需要采用锁机制,因此完全不需要担心死锁或是并发竞争的发生。

  5. 代码简洁

纯函数

不要修改传入的变量 对象 数组 全局参数等

  1. 没有副作用
  2. 易于测试 移植 并发执行 web worker
  3. 可缓存 可测试
const arr = [1, 2, 3]

arr.splice(0, 1) // 不纯 修改了arr

arr.map(i => i * 2) // 纯

我们应该尽量写纯函数,也可以通过一些手段把一些不纯的函数转成纯函数,常用方法是利用高阶函数

高阶函数

  1. 函数作为参数传入或作为返回值
  2. 高阶函数相当普遍,比如基于回调的各种函数 throttle debounce curry compose等等

Curry

在函数式编程中,curry随处可见,先看个例子

function add (a, b) {
  return a + b
}

const add2 = curry(add)

add2(2)(2) // 4

add2(1, 2) // 3

add2(1)()(2) // 3

这个就是curry 可以只传递给函数一部分参数来调用它,让他返回一个新的函数去处理剩下的参数。等到参数凑齐了,再返回结果

export const curry = (f, arity = f.length, ...args) =>
  args.length >= arity ? f(...args) : curry.bind(null, f, arity, ...args)

compose

const compose = function(f, g) {
  return function(x) {
    return f(g(x))
  }
}

f 和 g 都是函数, x 是在它们之间通过管道传输的值。

export function compose (...fns) {
  return function (x) {
    return fns.reduceRight((result, fn) => {
      return fn(result)
    }, x)
  }
}
import { compose, filter, sortBy } from 'lodash/fp'

const fromAdmin = filter({ type: 'admin' })
const sorted = sortBy('createdAt')
const unreadByUser = filter({ isReadByUser: false })

const groupedMessages = compose(
  sorted,
  unreadByUser,
  fromAdmin,
)(messages)

满足结合律

compose(f, compose(g, h)) == compose(compose(f, g), h) == compose(f, g, h)

Functor

在范畴论中,函子是范畴间的一类映射。函子也可以解释为小范畴范畴内的态射。

function Container (v) {
  this.value = v
}

Container.of = function (v) {
  return new Container(v)
}

Container 是个只有一个属性的对象,为了能操作这个容器中的属性,我们需要一种函数

Container.prototype.map = function(f){
  return Container.of(f(this.value))
}

Container.of(2).map(v => v * 2) // Container(4)

类似这样的Container,称作Functor,Functor 是实现了map函数并遵守一些特定规则的容器类型。

Maybe 函子

const Maybe = function (x) {
  this.value = x
}
Maybe.of = function (x) {
  return new Maybe(x)
}
Maybe.prototype.map = function (f) {
  return this.value ? Maybe.of(null) : Maybe.of(f(this.value))
}
Maybe.of({name: 'steins'}).map(_.property("age")).map(add(10)) // Maybe(null)

Maybe.of({name: 'steins'}).map(_.property("name")).map(console.log) // Maybe('steins')

IO函子

const IO = function (f) {
  this.value = f
}

IO.of = function (x) {
  return new IO(() => x)
}

IO.prototype.map = function (f) {
  return new IO(compose(f, this.value))
}

它的特点是接受的参数是一个函数,我们可以用它来实现惰性求值

const win = IO.of(window)

const split = curry((l, str) => str.split(l))

win.map(_.property('location')) // IO {value: f}

win.map(_.property('location')).map(_.property('href')).map(split('/')).value() // ['http:', '', 'lodash.com', 'docs', '4.17.11']

更多函子

Monod 主要用来处理副作用,因为还没弄懂,有兴趣请自行了解

参考文章

傻瓜函数式编程 js函数式编程指南 函数式编程入门