编程范式 —— 函数式编程入门
该系列会有 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 * xconst addAndMult = (x) => add(mult(x))
柯里化(curry)
如下是一个加法函数:
var add = (a, b, c) => a + b + cadd(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.lengthlet lists = args || []let listLenreturn function (..._args) {lists = [...lists, ..._args]listLen = lists.lengthif (listLen < length) {const that = listslists = []return curry(fn, ...that)} else if (listLen === length) {const that = listslists = []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
参考
- mostly-adequate-guide
- JavaScript 专题之函数柯里化
- 函数式编程入门教程
转载于:https://www.cnblogs.com/MuYunyun/p/10352716.html
编程范式 —— 函数式编程入门相关推荐
- 高阶函数||编程范式: 命令式编程/声明式编程 || 编程范式: 面向对象编程(第一公民:对象)/函数式编程(第一公民:函数)
编程范式: 命令式编程/声明式编程 编程范式: 面向对象编程(第一公民:对象)/函数式编程(第一公民:函数) 高阶函数 filter/map/reduce filter中的回调函数有一个要求: 必须返 ...
- java 函数式编程_函数式编程杂谈
比起命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断演进,逐层推导出复杂的运算.本文通过函数式编程的一些趣味用法来阐述学习函数式编程的奇妙之处. 一 ...
- 几段小代码解释Python命令式编程和函数式编程
所谓命令式编程,是指How to do,要通过指令告诉计算机如何一步一步地完成预定任务:而所谓函数式编程,可理解为What to do,只需要通过简单的指令告诉计算机要做什么就可以了,代码更加简洁.易 ...
- 【note】编程范式(编程范型)的含义和种类,多范式编程语言
范型 / 范式 = 模式.方法 编程范式 / 编程范型 = 编程的模式.风格 编程范式主要包括:结构化编程.面向对象编程.过程式(程序式)编程.函数式编程(泛函编程).指令式(命令式)编程.泛型编程. ...
- java什么是函数式编程,Java 函数式编程(一)初识篇
本文已授权"后端技术精选"独家发布. 开发者使用Java8编写复杂的集合处理算法,只需要简单的代码就能在多喝cpu上高效运行,这就是Lambda表达式的初衷. 提示:函数式编程和语 ...
- 链式编程和函数式编程
说起链式编程和函数式编程,小伙伴们千万不要紧张. 听着很高大尚,其实也就那么回事.相信有过C#开发经验的,或者其他编程经验的,只要不是OC,一看就知道. 看两行代码: 1 Person *person ...
- 函数式编程,函数式编程信奉那棵动态的运动树
cpu是树的动力源. 面向结构编程,面向结构编程所信奉的是努力设计那棵静态的资源树,相信那棵静态的资源树的良好可以大大降低那棵动态的运动树的复杂度.因为系统的资源树已经被提前进行了良好的设计,从而可以 ...
- 命令式编程与函数式编程
命令式编程 命令式编程(英语:Imperative programming),是一种描述电脑所需作出的行为的编程典范.几乎所有电脑的硬件工作都是指令式的:几乎所有电脑的硬件都是设计来运行机器码,使用指 ...
- java 函数式编程_Java函数式编程:Javaslang入门
java 函数式编程 Java是一门古老的语言,并且该领域中有很多新手在他们自己的领域(JVM)上挑战Java. 但是Java 8到来并带来了一些有趣的功能. 这些有趣的功能使编写新的惊人框架(例如S ...
最新文章
- tomcat启动时错误:Cannot rename original file to *.tomcat-users.xml.old
- centos根分区扩容方法linux公社,Centos5给/根分区扩容
- android 手动回收对象,Android Studio Studio回收列表中的JSON对象
- 模拟网页行为之实践篇三
- 吉林大学不如温州大学,泰晤士最新世界大学排名引发争议
- WinAPI: wvsprintf 与 wsprintf - Windows 的格式化输出函数
- 纸牌、挖金子源码链接
- html诗词赏析网页制作,html 网页文本设计
- 红米note4手机怎么屏幕录制视频
- 走向面试之数据库基础:一、你必知必会的SQL语句练习-Part 2
- 玩转数据可视化之R语言ggplot2:(四)单一基础几何图形绘制
- MathorCup大数据挑战赛第一届A题-移动通信基站流量预测赛题解析
- 51学工坊整理|甲骨文Oracle数据库 21c来了,来看看有哪些创新技术
- java for class_Class forClass
- Precision 3561 初体验
- 【Aegisub相关】template table 和 template environment table
- 混合动力汽车实时能量管理策略
- vue 和nodejs是什么关系?
- IDL常见问题与总结
- kaldi中文语音识别_基于thchs30(7)
热门文章
- 《scikit-learn》随机森林之深入学习
- 去哪儿-22-async-components
- 将rm -f or -rf 删除命令改为放入回收站,并可通过命令将其撤回
- Delphi以GDI+制作桌面歌词效果
- 如何制作一个塔防游戏 Cocos2d x 2 0 4
- 正则表达式必知必会学习笔记
- Gym 102798A(思维) acm寒假集训日记21/12/31or22/1/1
- 【C语言深入】[002] valotile 关键字:
- android 半边圆角背景,Android UI(一)Layout 背景局部Shape圆角设计
- swift文档_Swift 正式进入 Windows 平台