JavaScript函数式编程之函子
函子(Functor)
函子是一个特殊的容器,通过一个普通对象来实现,该对象具有map
方法,map
方法可以运行一个函数对值进行处理(变形关系),容器
包含值和值变形关系(这个变形关系就是函数)。函数式编程中解决副作用的存在
- 函数式编程的运算不直接操作值,,而是由函子完成
- 函子就是一个实现了
map
契约的对象 - 我们可以把函子想象成一个盒子,盒子里面封装了一个值
- 想要处理盒子中的值,我们需要给盒子的
map
方法传递一个处理值的函数(纯函数),由这个函数来对值进行处理 - 最终map方法返回一个包含新值所在的盒子(函子)
根据函子的定义我们创建一个函子
// functor 函子
class Container {
constructor (value) {
// 函子内部保存这个值。下划线是不想外部访问
this._value = value
}
// map 方法接收一个处理值的函数
map (fn) {
return new Container(fn(this._value))
}
}
此时就已经创建了一个函子但是这是面向对象的方式来创建的,换成用函数式编程来写一个函子
class Container {
constructor (value) {
this._value = value
}
map (fn) {
return Container.of(fn(this._value))
}
static of (value) {
return new Container(value)
}
}
let x = Container.of(5).map(x => x + 1).map(x => x - 1)
但是这个函子还是存在一些问题,比如空值的时候就会报错, 会让我们的函子变的不纯,我们需要去拦截空值错误,我们创建一个方法去判断是否为空值,如果是控制我们直接返回一个空值的函子,如果有值再去处理,这个时候就需要使用MayBe
函子
let x = Container.of(null).map(x => x + 1).map(x => x - 1)
MayBe 函子
我们在编程的过程中可能会遇到很多错误,需要对这些错误做相应的处理,MayBe
函子的作用就是可以对外部的空值情况做处理(控制副作用在允许的范围)
// MayBe 函子
class MayBe {
constructor (value) {
this._value = value
}
map (fn) {
return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
}
isNothing () {
return this._value === undefined || this._value === null
}
static of (value) {
return new MayBe(value)
}
}
let x = MayBe.of(null)
.map(x => x + 1)
.map(x => x - 1)
console.log(x)
这个时候我们已经能正常执行了,但是现在出现了空值的函子,但是我们不知道那个地方出现了空值,所以我们创建两个函子一个是正常的处理一个是出现错误情况处理,正常的就按照正常的方式创建,错误的是是否我们把map
方法改造一下让她不再处理回调函数,直接返回一个空值的MayBe
函子,这样就记录下了错误信息Eitcher
函子就是来处理这种情况的
Either函子
Eitcher
类似于 if else
的处理,两者中的任何一个,异常会让函数变的不纯,Eitcher
函子可以用来做异常处理
// 因为是二选一,所以定义两个类 Left 和 Right
// 记录错误信息的
class Left {
constructor (value) {
this._value = value
}
map (fn) {
return this
}
static of (value) {
return new Left(value)
}
}
// 正常处理
class Rgiht {
constructor (value) {
this._value = value
}
map (fn) {
return Rgiht.of(fn(this._value))
}
static of (value) {
return new Rgiht(value)
}
}
function parseJson (str) {
try {
return Rgiht.of(JSON.parse(str))
} catch (err) {
return Left.of({ message: err.message })
}
}
// 故意传入错误的数据
let r = parseJson('{ name: "2" }')
r.map(x => x.name.toUpperCase())
console.log(r)
IO 函子
IO
函子中的 _value
是一个函数, 这里把函数作为值来处理, IO 函子可以吧不纯的动作储存到_value
中,延迟这个不纯的操作(惰性执行),保证当前的操作是纯的,延迟把不纯的操作到调用者来处理
const fp = require('lodash/fp')
// IO 函子
class IO {
constructor (fn) {
this._value = fn
}
static of (value) {
return new IO(function () {
return value
})
}
map (fn) {
// 把当前的value 和传入的fn 函数组合成一个新的函数
return new IO(fp.flowRight(fn, this._value))
}
}
let r = IO.of(process).map(x => x.execPath)
console.log(r)
console.log(r._value())
IO 函子内部帮我们包装了一些函数,当我们传递函数的时候有可能这个函数是一个不纯的操作,不管这个函数纯与不纯,IO这个函子在执行的过程中它返回的这个结果始终是一个纯的操作,我们调用map
的时候始终返回的是一个函子,但是IO
函子这个_value
属性他里面要去合并很多函数,所以他里面可能是不纯的,把这些不纯的操作延迟到了调用的时候,也就是我们通过IO
函子控制了副作用的在可控的范围内发生
实现 liunx 下 cat 命令
const fp = require('lodash/fp')
// IO 函子
class IO {
constructor (fn) {
this._value = fn
}
static of (value) {
return new IO(function () {
return value
})
}
map (fn) {
// 把当前的value 和传入的fn 函数组合成一个新的函数
return new IO(fp.flowRight(fn, this._value))
}
}
let r = IO.of(process).map(x => x.execPath)
function readFile (fileName) {
return new IO(() => fs.readFileSync(fileName, 'utf-8'))
}
function print (x) {
return new IO(() => {
console.log(x)
return x
})
}
let cat = fp.flowRight(print, readFile)
console.log(cat('package.json')._value()._value())
此时IO
函子出现了嵌套的问题,导致调用嵌套函子中的方法就必须要要._value()._value()
这样来执了,嵌套了几层就需要几层调用
Folktale
Folktale 是一个标准的函数式编程库,和lodash
不同的是,他没有提供很多功能函数,只提供了一些函数式处理的操作,例如:compose、curry
等,一些函子 Task、Either、MayBe
等,
Folktale 中的curry
与compose
的简单使用
const { compose, curry } = require('folktale/core/lambda')
const { toUpper, first } = require('lodash/fp')
// 与lodash区别,第一个参数指明后面参数的个数
let f = curry(2, (n1, n2) => n1 + n2)
console.log(f(1, 2))
// compose 就是函数组合 lodash 中的函数组合是 flowRight
let f2 = compose(toUpper, first)
console.log(f2(['one', 'two']))
Folktale 中的 task 函子
函子可以处理异步任务,在异步任务中会通往地狱之门的回调,而使用task
函子可以避免回调的嵌套,详细请看官方文档
// Task 异步任务
const { task } = require('folktale/concurrency/task')
const { split, find } = require('lodash/fp')
const fs = require('fs')
function readFile (filename) {
return task(resolver => {
fs.readFile(filename, 'utf-8', (err, data) => {
if (err) {
resolver.reject(err)
}
resolver.resolve(data)
})
})
}
readFile('package.json')
.map(split('\n'))
.map(find(x => x.includes('version')))
// 执行读取文件
.run()
.listen({
onRejected(err) {
console.log(err)
},
onResolved(value) {
console.log(value)
}
})
Pointed函子
Pointed函子 是实现了of静态方法, of 方法是为了避免使用new 来创建对象,更深层次含义是of方法把值放到上下文Context
(把值放到容器中,使用map
来处理值)
class Container {
constructor (value) {
this._value = value
}
static of () {
return new Container(value)
}
map (fn) {
return new Container(fn(this._value))
}
}
Monad函子
解决函子嵌套的问题,Monad
函子是可以变扁的 Pointed
函子 IO(IO)
,一个函子如果具有join
和of
两个方法并遵循一些定律就是一个Monad
class IO {
constructor (fn) {
this._value = fn
}
static of (value) {
return new IO(function () {
return value
})
}
map (fn) {
return new IO(fp.flowRight(fn, this._value))
}
join () {
return this._value()
}
// 同时调用 join 和 map
flatMap (fn) {
return this.map(fn).join()
}
}
function readFile (fileName) {
return new IO(() => fs.readFileSync(fileName, 'utf-8'))
}
function print (x) {
return new IO(() => {
return x
})
}
let r = readFile('package.json').flatMap(print).join()
console.log(r)
当我们想要去调用一个方法,这个方法返回一值的时候我们去调用map
方法,当我们想要去调用一个方法,这个方法返回一个函子的时候我们去调用flatMap
方法
原文地址:https://kspf.xyz/archives/17
更多内容微信公众号搜索充饥的泡饭
小程序搜一搜开水泡饭的博客
JavaScript函数式编程之函子的更多相关文章
- 转: JavaScript函数式编程(二)
转: JavaScript函数式编程(二) 作者: Stark伟 上一篇文章里我们提到了纯函数的概念,所谓的纯函数就是,对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环 ...
- 转:JavaScript函数式编程(三)
转:JavaScript函数式编程(三) 作者: Stark伟 这是完结篇了. 在第二篇文章里,我们介绍了 Maybe.Either.IO 等几种常见的 Functor,或许很多看完第二篇文章的人都会 ...
- 转:JavaScript函数式编程(一)
转:JavaScript函数式编程(一) 一.引言 说到函数式编程,大家可能第一印象都是学院派的那些晦涩难懂的代码,充满了一大堆抽象的不知所云的符号,似乎只有大学里的计算机教授才会使用这些东西.在曾经 ...
- JavaScript 函数式编程读书笔记2
概述 这是我读<javascript函数式编程>的读书笔记,供以后开发时参考,相信对其他人也有用. 说明:虽然本书是基于underscore.js库写的,但是其中的理念和思考方式都讲的很好 ...
- JavaScript 函数式编程读书笔记1
概述 这是我读<javascript函数式编程>的读书笔记,供以后开发时参考,相信对其他人也有用. 说明:虽然本书是基于underscore.js库写的,但是其中的理念和思考方式都讲的很好 ...
- 一文带你了解JavaScript函数式编程
摘要: 函数式编程入门. 作者:浪里行舟 Fundebug经授权转载,版权归原作者所有. 前言 函数式编程在前端已经成为了一个非常热门的话题.在最近几年里,我们看到非常多的应用程序代码库里大量使用着函 ...
- javascript函数式编程和链式优化
1.函数式编程理解 函数式编程可以理解为,以函数作为主要载体的编程方式,用函数去拆解.抽象一般的表达式 与命令式相比,这样做的好处在哪?主要有以下几点: (1)语义更加清晰 (2)可复用性更高 (3) ...
- JavaScript函数式编程(纯函数、柯里化以及组合函数)
JavaScript函数式编程(纯函数.柯里化以及组合函数) 前言 函数式编程(Functional Programming),又称为泛函编程,是一种编程范式.早在很久以前就提出了函数式编程这个概念了 ...
- 在JavaScript函数式编程里使用Map和Reduce方法
所有人都谈论道workflows支持ECMAScript6里出现的令人吃惊的新特性,因此我们很容易忘掉ECMAScript5带给我们一些很棒的工具方法来支持在JavaScript里进行函数编程,这些工 ...
随机推荐
- Python 工匠: 异常处理的三个好习惯
前言 这是 "Python 工匠"系列的第 6 篇文章.(点击原文链接,可查看系列其他文章) 如果你用 Python 编程,那么你就无法避开异常,因为异常在这门语言里无处不在.打个 ...
- PerfView专题 (第二篇):如何寻找 C# 中的 Heap堆内存泄漏
一:背景 上一篇我们聊到了如何去找 热点函数,这一篇我们来看下当你的程序出现了 非托管内存泄漏 时如何去寻找可疑的代码源头,其实思路很简单,就是在 HeapAlloc 或者 VirtualAlloc ...
- Vite + TS 项目导入 jQuery 包时报错:Could not find a declaration file
TypeScript 需要类型标注,当使用第三方库(除 ts 以外写的库,即 js)时,又缺少声明文件,我们需要引用它的声明文件,才能获得对应的代码补全.接口提示等功能.jQuery 不是 TypeS ...
- Python小游戏——外星人入侵(保姆级教程)第一章 07调整飞船速度 08限制飞船活动范围
系列文章目录 第一章:武装飞船 07调整飞船速度 08限制飞船活动范围 一.代码及演示 1.修改settings 修改文件:settings.py 点击查看代码 #渗透小红帽python的学习之路 # ...
- 【JavaWeb】学习路径1-背景
JavaWeb系列也是一个非常庞大的系列,主要分为五个部分讲解: HTML JSP和Servlet CSS的讲解 JavaScrip的讲解 jQuery框架的讲解 学习完上述内容后,就能够基本了解一个 ...
- QtCreator像C# region一样折叠代码
C# #region "comment" [code] #endregion 就可以在VS中实现代码折叠了 QtCreator #pragma region "comme ...
- 手把手教你搭建规范的团队vue项目,包含commitlint,eslint,prettier,husky,commitizen等等
目录 1,前言 2,创建项目 2,安装vue全家桶 3,配置prettier 4,配置eslint 5,配置husky + git钩子 6,配置commitlint 6.1,配置commitlint格 ...
- LibTorch 自动微分
得益于反向传播算法,神经网络计算导数时非常方便,下面代码中演示如何使用LibTorch进行自动微分求导. 进行自动微分运算需要调用函数 torch::autograd::grad( outputs, ...
- MySQL半同步复制源码解析
今天 DBA 同事问了一个问题,MySQL在半同步复制的场景下,当关闭从节点时使得从节点的数量 < rpl_semi_sync_master_wait_for_slave_count时,show ...
- TortoiseSVN 执行清理( cleanUp )失败的解决方案
TortoiseSVN 执行清理( cleanUp )失败的解决方案 今天碰到了一个比较棘手的问题,在这里做一下记录,以方便自己和有需要的朋友在之后碰到该类问题时有个参考. 现象 更新SVN时弹出清理 ...