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 和 mapflatMap (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函数式编程入门经典
一个持续更新的github笔记,链接地址:Front-End-Basics,可以watch,也可以star. 此篇文章的地址:JavaScript函数式编程入门经典 正文开始 什么是函数式编程?为何它 ...
- JavaScript函数式编程(二)
上一篇文章 JavaScript函数式编程(一) 里我们提到了纯函数的概念,所谓的纯函数就是,对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态(我偷懒复制过 ...
- JavaScript函数式编程(一)\(二)\(三)
JavaScript函数式编程(一) Starkwang 前端工程师@腾讯 / Node.js Collaborator 一.引言 说到函数式编程,大家可能第一印象都是学院派的那些晦涩难懂的代码,充满 ...
- JavaScript 函数式编程(一)
零.前言 说到函数式编程,想必各位或多或少都有所耳闻,然而对于函数式的内涵和本质可能又有些说不清楚. 所以本文希望针对工程师,从应用(而非学术)的角度将函数式编程相关思想和实践(以 JavaScrip ...
- JavaScript函数式编程的一些理解
最近在看JavaScript函数式编程的部分,有些阅读之后的心得和理解,我想总结并分享出来. 首先是什么是函数式编程?函数式编程实际上与过程式编程一样,是一种编程的风格.函数式编程是以函数作为第一公民 ...
- JavaScript 函数式编程(三)
slide 地址 四.Talk is cheap!Show me the ... MONEY! 以下内容主要参考自 Professor Frisby Introduces Composable Fun ...
- 我眼中的JavaScript函数式编程
JavaScript 函数式编程是一个存在了很久的话题,但似乎从 2016 年开始,它变得越来越火热.这可能是因为 ES6 语法对于函数式编程更为友好,也可能是因为诸如 RxJS (ReactiveX ...
- JavaScript函数式编程之深入理解纯函数
更多相关内容见博客 https://github.com/zhuanyongxigua/blog 纯函数是函数式编程的基础,需要重点理解. 纯函数的概念: 纯函数是这样一种函数,即相同的输入,永远会得 ...
- SegmentFault 技术周刊 Vol.16 - 浅入浅出 JavaScript 函数式编程
函数式编程(Functional Programming),一看这个词,简直就是学院派的典范. 以至于从 Lisp 的创世,到 Scheme.Haskell.Clean.Erlang.Miranda. ...
最新文章
- 宏观经济学思维导图_巧用思维导图,提升初三化学专题复习课实效
- Cifar10与ResNet18实战、lenet5、resnet(学习笔记)
- java注解传递value_spring中@value注解需要注意的问题
- nio的应用 java_Java NIO 在网络编程中的应用
- 前端工程师必备谷歌浏览器F12下的调试知识点
- js实现svg图形转存为图片下载
- .net函数查询_SQL查询语句总是先执行SELECT?你们都错了!
- WPF中Auto与*的区别
- Kafka—topic的查询和创建
- spark通过合理设置spark.default.parallelism参数提高执行效率
- 毕设/私活/必备,一个挣钱的标准开源前后端分离【springboot+vue+redis+Spring Security】脚手架--若依框架
- 微信小程序获取二维码:报错47001 data format error
- 如何运用js制作简单的登录界面(html)
- 为什么用格式刷不能复制行距_如何使用格式刷在PowerPoint中复制格式
- Audio解析strategy配置文件
- 【CSP-S 2019模拟】题解
- 【解决方案】SkeyeVSS综合安防视频云服务在零售连锁巡店中的应用
- 小括号、中括号、大括号(正则表达式)
- putchar、getchar 大小写转化
- C语言system()函数