浅学函数式编程

2020-06-20  本文已影响0人  望月从良glh
函数式编程.png

函数式编程基本概念

为什么学习函数式编程

基本概念

函数式编程(Functional Programming, FP),FP 是编程范式之一,我们常听说的编程范式还有面向过程编程、面向对象编程。
面向对象编程的思维方式:把现实世界中的事物抽象成程序世界中的类和对象,通过封装、继承和 多态来演示事物事件的联系
程序的本质:根据输入通过某种运算获得相应的输出、程序开发过程中会涉及很多有输入和输出的函数

函数是一等公民

高阶函数

基本概念

// forEach 
 function forEach (array, fn) { 
   for (let i = 0; i < array.length; i++) { 
       fn(array[i]) 
   } 
 }
 // filter 
 function filter (array, fn) { 
 let results = [] 
 for (let i = 0; i < array.length; i++) { 
           if (fn(array[i])) { 
           results.push(array[i]) 
       } 
   }
   return results 
 }
  function makeFn () { 
    let msg = 'Hello function' 
    return function () { 
        console.log(msg) 
    } 
  }
  const fn = makeFn() 
  fn() 
  // once 
  function once (fn) { 
    let done = false 
    return function () { 
        if (!done) { 
            done = true 
            return fn.apply(this, arguments) 
        } 
    } 
  }
  let pay = once(function (money) { 
    console.log(`支付:${money} RMB`) 
  })
  // 只会支付一次 
  pay(5) 
  pay(5) 
  pay(5)

高阶函数意义

// 面向过程的方式 
let array = [1, 2, 3, 4] 
for (let i = 0; i < array.length; i++) { 
    console.log(array[i]) 
}
// 高阶函数 
let array = [1, 2, 3, 4] 
forEach(array, item => { 
    console.log(item) 
})
let r = filter(array, item => { 
    return item % 2 === 0 
})

常用的高阶函数

  const map = (array, fn) => { 
    let results = [] 
    for (const value of array) { 
        results.push(fn(value)) 
    }
    return results 
  }

  const every = (array, fn) => { 
    let result = true 
    for (const value of array) { 
        result = fn(value) 
        if (!result) { 
            break 
        } 
    }
    return result 
  }


  const some = (array, fn) => { 
    let result = false 
    for (const value of array) { 
        result = fn(value) 
        if (result) { 
            break 
        } 
    }
    return result 
  }

闭包

// 函数作为返回值 
function makeFn () { 
    let msg = 'Hello function' 
    return function () { 
        console.log(msg) 
    } 
}
const fn = makeFn() 
fn()
// 生成计算数字的多少次幂的函数 
function makePower (power) { 
    return function (x) { 
        return Math.pow(x, power) 
    }
}
let power2 = makePower(2) 
let power3 = makePower(3) 
console.log(power2(4)) 
console.log(power3(4))

纯函数

纯函数概念

let numbers = [1, 2, 3, 4, 5] 
// 纯函数 
numbers.slice(0, 3) 
// => [1, 2, 3] 
numbers.slice(0, 3) 
// => [1, 2, 3] 
numbers.slice(0, 3) 
// => [1, 2, 3]

// 不纯的函数 
numbers.splice(0, 3) 
// => [1, 2, 3] 
numbers.splice(0, 3) 
// => [4, 5] 
numbers.splice(0, 3) 
// => []

为什么要使用纯函数

  模拟lodash的memoize函数
  function memoize (f) { 
    let cache = {} 
    return function () { 
    let arg_str=JSON.stringify(arguments) 
        cache[arg_str] = cache[arg_str] || f.apply(f, arguments) 
        return cache[arg_str] 
    } 
  }

副作用的理解

// 不纯的 
let mini = 18 
function checkAge (age) { 
    return age >= mini 
}
// 纯的(有硬编码,后续可以通过柯里化解决) 
function checkAge (age) { 
    let mini = 18 
    return age >= mini 
}

总结:如果依赖外部的状态就无法保证相同的输入得到相同的输出、且副作用使得方法的通用性下降、给程序带来不确定性,但是完全静止副作用是不可能的、尽量控制在可控范围内

柯里化(Curry)

// 普通纯函数 
function checkAge (min, age) { 
   return age >= min 
}
checkAge(18, 24) 
checkAge(18, 20) 
checkAge(20, 30) 
// 柯里化之后
let checkAge = min => (age => age >= min)
let checkAge18 = checkAge(18) 
let checkAge20 = checkAge(20) 
checkAge18(24)
checkAge18(20)

概念

lodash中的柯里化函数_.curry(fn)

// 要柯里化的函数 
function getSum (a, b, c) { 
    return a + b + c 
}
// 柯里化后的函数 
let curried = _.lodash(getSum) 
// 测试 
curried(1, 2, 3) 
curried(1)(2)(3) 
curried(1, 2)(3)
  function curry (func) { 
    return function curriedFn (...args) { 
  // 判断实参和形参的个数 
        if (args.length < func.length) { 
            return function () {
                return
  curriedFn(...args.concat(Array.from(arguments))) 
            } 
        }
  // 实参和形参个数相同,调用 func,返回结果 
        return func(...args) 
    } 
  }

总结

函数组合

// 组合函数 
function compose (f, g) { 
    return function (x) { 
        return f(g(x)) 
    } 
}
function first (arr) { 
    return arr[0] 
}
function reverse (arr) { 
    return arr.reverse() 
}
// 从右到左运行 
let last = compose(first, reverse) 
console.log(last([1, 2, 3, 4]))

为什么要函数组合

管道思想

概念

特性

  const _ = require('lodash') 
  const trace = _.curry((tag, v) => { 
  console.log(tag, v) 
  return v 
  })
  const split = _.curry((sep, str) => _.split(str, sep)) 
  const join = _.curry((sep, array) => _.join(array, sep)) 
  const map = _.curry((fn, array) => _.map(array, fn)) 
  const f = _.flowRight(join('-'),trace('map 之后'), map(_.toLower), trace('split 之后'), split(' ')) 
  console.log(f('NEVER SAY DIE'))
  // 结合律 

  const _ = require('lodash') 
  // const f = _.flowRight(_.toUpper, _.first, _.reverse) 
  // const f = _.flowRight(_.flowRight(_.toUpper, _.first), _.reverse) 
  const f = _.flowRight(_.toUpper, _.flowRight(_.first, _.reverse)) 
  console.log(f(['one', 'two', 'three'])) 
  // => THREE

函数组合实现原理

// ES6 
const compose = (...fns) => value =>    fns.reverse().reduce((acc, fn) => fn(acc), 
value)
// ES5
function compose (...fns) { 
    return function (value) { 
        return fns.reverse().reduce(function (acc, fn) { 
        return fn(acc) 
        }, value) 
    } 
}

Point Free

// 非 Point Free 模式 
// Hello World => hello_world 
function f (word) { 
    return word.toLowerCase().replace(/\s+/g, '_'); 
}
// Point Free 
const fp = require('lodash/fp') 
const f = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower) 
console.log(f('Hello World'))

Functor(函子)

概念

// 一个容器,包裹一个值 
class Container { 
// of 静态方法,可以省略 new 关键字创建对象 
static of (value) { 
return new Container(value) 
}
constructor (value) { 
this._value = value 
}
// map 方法,传入变形关系,将容器里的每一个值映射到另一个容器 
map (fn) { 
return Container.of(fn(this._value)) 
} 
}
// 测试 
Container.of(3) 
.map(x => x + 2) 
.map(x => x * x)

MayBe 函子

class MayBe { 
static of (value) { 
return new MayBe(value) 
}
constructor (value) { 
this._value = value 
}
// 如果对空值变形的话直接返回 值为 null 的函子 
map (fn) { 
return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value)) 
}
isNothing () { 
return this._value === null || this._value === undefined 
} 
}
// 传入具体值 
MayBe.of('Hello World') 
.map(x => x.toUpperCase()) 
// 传入 null 的情况 
MayBe.of(null) 
.map(x => x.toUpperCase()) 
// => MayBe { _value: null }

Either 函子

class Left { 
static of (value) { 
return new Left(value) 
}
constructor (value) { 
this._value = value 
}
map (fn) { 
return this 
} 
}
class Right { 
static of (value) { 
return new Right(value)
}
constructor (value) { 
this._value = value 
}
map(fn) { 
return Right.of(fn(this._value)) 
} 
}
function parseJSON(json) { 
try { 
return Right.of(JSON.parse(json)); 
} catch (e) { 
return Left.of({ error: e.message}); 
} 
}
// Either 用来处理异常
let r = parseJSON('{ "name": "zs" }') 
.map(x => x.name.toUpperCase()) 
console.log(r)

IO 函子

const fp = require('lodash/fp') 
class IO { 
static of (x) { 
return new IO(function () { 
return x 
}) 
}
constructor (fn) { 
this._value = fn 
}
map (fn) { 
// 把当前的 value 和 传入的 fn 组合成一个新的函数 
return new IO(fp.flowRight(fn, this._value)) 
} 
}
// 调用 
let io = IO.of(process).map(p => p.execPath) 
console.log(io._value())

Pointed 函子

class Container { 
static of (value) { 
return new Container(value) 
}
…… 
}
Contanier.of(2) 
.map(x => x + 5)

Monad 函子

const fp = require('lodash/fp') 
// IO Monad 
class IO { 
static of (x) { 
return new IO(function () { 
return x 
}) 
}
constructor (fn) { 
this._value = fn 
}
map (fn) { 
return new IO(fp.flowRight(fn, this._value)) 
}
join () { 
return this._value() 
}
flatMap (fn) { 
return this.map(fn).join() 
} 
}
let r = readFile('package.json') 
.map(fp.toUpper) 
.flatMap(

Task 函子(folktale库)

const { task } = require('folktale/concurrency/task') 
function readFile(filename) { 
return task(resolver => { 
fs.readFile(filename, 'utf-8', (err, data) => { 
if (err) resolver.reject(err) 
resolver.resolve(data) 
}) 
}) 
}
// 调用 run 执行 
readFile('package.json') 
.map(split('\n')) 
.map(find(x => x.includes('version'))) 
.run().listen({ 
onRejected: err => { 
console.log(err) 
},
onResolved: value => { 
console.log(value) 
} 
})

总结

参考文献

上一篇 下一篇

猜你喜欢

热点阅读