内容简介:该系列会有 3 篇文章,分别介绍什么是函数式编程、剖析函数式编程库、以及函数式编程在 React 中的应用,欢迎关注我的拿泡茶这个事例进行区分命令式编程和声明式编程
该系列会有 3 篇文章,分别介绍什么是函数式编程、剖析函数式编程库、以及函数式编程在 React 中的应用,欢迎关注我的 blog
命令式编程和声明式编程
拿泡茶这个事例进行区分命令式编程和声明式编程
- 命令式编程
1.烧开水(为第一人称)
2.拿个茶杯
3.放茶叶
4.冲水
- 声明式编程
1.给我泡杯茶(为第二人称)
举个 demo
// 命令式编程
const convert = function(arr) {
const result = []
for (let i = 0; i < arr.length; i++) {
result[i] = arr[i].toLowerCase()
}
return result
}
// 声明式编程
const convert = function(arr) {
return arr.map(r => r.toLowerCase())
}
什么是函数式编程
函数式编程是声明式编程的范式。在函数式编程中数据在由纯函数组成的管道中传递。
函数式编程可以用简单如 交换律、结合律、分配律 的数学之法来帮我们简化代码的实现。
它具有如下一些特性:
- 纯粹性: 纯函数不改变除当前作用域以外的值;
// 反面示例 let a = 0 const add = (b) => a = a + b // 两次 add(1) 结果不一致 // 正确示例 const add = (a, b) => a + b
- 数据不可变性: Immutable
// 反面示例
const arr = [1, 2]
const arrAdd = (value) => {
arr.push(value)
return arr
}
arrAdd(3) // [1, 2, 3]
arrAdd(3) // [1, 2, 3, 3]
// 正面示例
const arr = [1, 2]
const arrAdd = (value) => {
return arr.concat(value)
}
arrAdd(3) // [1, 2, 3]
arrAdd(3) // [1, 2, 3]
在后记 1 中对数组字符串方法是否对原值有影响作了整理
- 函数柯里化: 将多个入参的函数转化为一个入参的函数;
const add = a => b => c => a + b + c add(1)(2)(3)
- 偏函数: 将多个入参的函数转化成两部分;
const add = a => (b, c) => a + b + c add(1)(2, 3)
- 可组合: 函数之间能组合使用
const add = (x) => x + x const mult = (x) => x * x const addAndMult = (x) => add(mult(x))
柯里化(curry)
如下是一个加法函数:
var add = (a, b, c) => a + b + c add(1, 2, 3) // 6
假如有这样一个 curry 函数, 用其包装 add 函数后返回一个新的函数 curryAdd , 我们可以将参数 a、b 进行分开传递进行调用。
var curryAdd = curry(add) // 以下输出结果都相同 curryAdd(1, 2, 3) // 6 curryAdd(1, 2)(3) // 6 curryAdd(1)(2)(3) // 6 curryAdd(1)(2, 3) // 6
动手实现一个 curry 函数
核心思路: 若传进去的参数个数未达到 curryAdd 的个数,则将参数缓存在闭包变量 lists 中:
function curry(fn, ...args) {
const length = fn.length
let lists = args || []
let listLen
return function (..._args) {
lists = [...lists, ..._args]
listLen = lists.length
if (listLen < length) {
const that = lists
lists = []
return curry(fn, ...that)
} else if (listLen === length) {
const that = lists
lists = []
return fn.apply(this, that)
}
}
}
代码组合(compose)
现在有 toUpperCase 、 reverse 、 head 三个函数, 分别如下:
var toUpperCase = (str) => str.toUpperCase() var reverse = (arr) => arr.reverse() var head = (arr) => arr[0]
接着使用它们实现将数组末位元素大写化输出, 可以这样做:
var reverseHeadUpperCase = (arr) => toUpperCase(head(reverse(arr))) reverseHeadUpperCase(['apple', 'banana', 'peach']) // "PEACH"
此时在构建 reverseHeadUpperCase 函数的时候, 必须手动声明传入参数 arr, 是否能提供一个 compose 函数让使用者更加友好的使用呢? 类似如下形式:
var reverseHeadUpperCase = compose(toUpperCase, head, reverse) reverseHeadUpperCase(['apple', 'banana', 'peach']) // "PEACH"
此外 compose 函数符合 结合律 , 我们可以这样子使用:
compose(compose(toUpperCase, head), reverse) compose(toUpperCase, compose(head, reverse))
以上两种写法与 compose(toUpperCase, head, reverse) 的效果完全相同, 都是依次从右到左执行传参中的函数。
此外 compose 和 map 一起使用时也有相关的结合律, 以下两种写法效果相等
compose(map(f), map(g)) map(compose(f, g))
动手实现一个 compose 函数
代码精华集中在一行之内, 其为众多开源库(比如 Redux) 所采用。
var compose = (...args) => (initValue) => args.reduceRight((a, c) => c(a), initValue)
范畴论
范畴论是数学中的一个分支。可以将范畴理解为一个容器, 把原来对值的操作,现转为对容器的操作。如下图:
学习函数式编程就是学习各种函子的过程。
函数式编程中, 函子(Functor) 是实现了 map 函数的容器, 下文中将函子视为范畴,模型可表示如下:
class Functor {
constructor(value) {
this.value = value
}
map(fn) {
return new Functor(fn(this.value))
}
}
但是在函数式编程中, 要避免使用 new 这种面向对象的编程方式, 取而代之对外暴露了一个 of 的接口, 也称为 pointed functor 。
Functor.of = value => new Functor(value)
Maybe 函子
Maybe 函子 是为了解决 this.value 为 null 的情形, 用法如下:
Maybe.of(null).map(r => r.toUpperCase()) // null
Maybe.of('m').map(r => r.toUpperCase()) // Maybe {value: "M"}
实现代码如下:
class Maybe {
constructor(value) {
this.value = value
}
map(fn) {
return this.value ? new Maybe(fn(this.value)) : null
}
}
Maybe.of = value => new Maybe(value)
Either 函子
Either 函子 是为了对应 if...else... 的语法, 即 非左即右 。因此可以将之拆分为 Left 和 Right 两个函子, 它们的用法如下:
Left.of(1).map(r => r + 1) // Left {value: 1}
Right.of(1).map(r => r + 1) // Right {value: 2}
Left 函子 实现代码如下:
class Left {
constructor(value) {
this.value = value
}
map(fn) {
return this
}
}
Left.of = value => new Left(value)
Right 函子 实现代码如下(其实就是上面的 Functor ):
class Right {
constructor(value) {
this.value = value
}
map(fn) {
return new Right(fn(this.value))
}
}
Right.of = value => new Right(value)
具体 Either 函数只是对调用 Left 函子 或 Right 函子 作一层筛选, 其接收 f 、 g 两个函数以及一个函子( Left or Right )
var Either = function(f, g, functor) {
switch(functor.constructor) {
case 'Left':
return f(functor.value)
case 'Right':
return g(functor.value)
default:
return f(functor.value)
}
}
使用 demo:
Either((v) => console.log('left', v), (v) => console.log('def', v), left) // left 1
Either((v) => console.log('rigth', v), (v) => console.log('def', v), rigth) // rigth 2
Monad 函子
函子会发生嵌套, 比如下面这样:
Functor.of(Functor.of(1)) // Functor { value: Functor { value: 1 } }
Monad 函子 对外暴露了 join 和 flatmap 接口, 调用者从而可以扁平化嵌套的函子。
class Monad {
constructor(value) {
this.value = value
}
map(fn) {
return new Monad(fn(this.value))
}
join() {
return this.value
}
flatmap(fn) {
return this.map(fn).join()
}
}
Monad.of = value => new Monad(value)
使用方法:
// join
Monad.of(Monad.of(1).join()) // Monad { value: 1 }
Monad.of(Monad.of(1)).join() // Monad { value: 1 }
// flatmap
Monad.of(1).flatmap(r => r + 1) // 2
Monad 函子可以运用在 I/O 这种不纯的操作上将之变为纯函数的操作,目前比较懵懂,日后补充。
后记 1: 数组字符串方法小结(是否对原值有影响)
不会对原数组有影响的方法
slice
var test = [1, 2, 3] var result = test.slice(0, 1) console.log(test) // [1, 2, 3] console.log(result) // [1]
concat
var test = [1, 2, 3] var result = test.concat(4) console.log(test) // [1, 2, 3] console.log(result) // [1, 2, 3, 4]
对原数组有影响的方法
splice(这个需要特别记一下)
var test = [1, 2, 3] var result = test.splice(0, 1) console.log(test) // [2, 3] console.log(result) // [1]
sort
var arr = [2, 1, 3, 4] arr.sort((r1, r2) => (r1 - r2)) console.log(arr) // [1, 2, 3, 4]
reverse
var test = [1, 2, 3] var result = test.reverse() console.log(test) // [3, 2, 1] console.log(result) // [3, 2, 1]
push/pop/unshift/shift
var test = [1, 2, 3] var result = test.push(4) console.log(test) // [1, 2, 3, 4] console.log(result) // 4
不会对原字符串造成影响的方法
substr/substring/slice
// substr var test = 'abc' var result = test.substr(0, 1) console.log(test) // 'abc' console.log(result) // a // substring var test = 'abc' var result = test.substring(0, 1) console.log(test) // 'abc' console.log(result) // a // slice var test = 'abc' var result = test.slice(0, 1) console.log(test) // 'abc' console.log(result) // a
参考
以上所述就是小编给大家介绍的《编程范式 —— 函数式编程入门》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- React — 端的编程范式
- Rust语言的编程范式
- React 下,谈谈编程范式
- 浅谈声明式编程范式
- Kubernetes编程范式——Controller pattern
- Scala 2.13.2 发布,多范式编程语言
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
神经网络与机器学习(原书第3版)
[加] Simon Haykin / 申富饶、徐烨、郑俊、晁静 / 机械工业出版社 / 2011-3 / 79.00元
神经网络是计算智能和机器学习的重要分支,在诸多领域都取得了很大的成功。在众多神经网络著作中,影响最为广泛的是Simon Haykin的《神经网络原理》(第3版更名为《神经网络与机器学习》)。在本书中,作者结合近年来神经网络和机器学习的最新进展,从理论和实际应用出发,全面、系统地介绍了神经网络的基本模型、方法和技术,并将神经网络和机器学习有机地结合在一起。 本书不但注重对数学分析方法和理论的探......一起来看看 《神经网络与机器学习(原书第3版)》 这本书的介绍吧!