函子(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 中的currycompose的简单使用

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),一个函子如果具有joinof两个方法并遵循一些定律就是一个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函数式编程之函子相关推荐

  1. JavaScript函数式编程入门经典

    一个持续更新的github笔记,链接地址:Front-End-Basics,可以watch,也可以star. 此篇文章的地址:JavaScript函数式编程入门经典 正文开始 什么是函数式编程?为何它 ...

  2. JavaScript函数式编程(二)

    上一篇文章 JavaScript函数式编程(一)   里我们提到了纯函数的概念,所谓的纯函数就是,对于相同的输入,永远会得到相同的输出,而且没有任何可观察的副作用,也不依赖外部环境的状态(我偷懒复制过 ...

  3. JavaScript函数式编程(一)\(二)\(三)

    JavaScript函数式编程(一) Starkwang 前端工程师@腾讯 / Node.js Collaborator 一.引言 说到函数式编程,大家可能第一印象都是学院派的那些晦涩难懂的代码,充满 ...

  4. JavaScript 函数式编程(一)

    零.前言 说到函数式编程,想必各位或多或少都有所耳闻,然而对于函数式的内涵和本质可能又有些说不清楚. 所以本文希望针对工程师,从应用(而非学术)的角度将函数式编程相关思想和实践(以 JavaScrip ...

  5. JavaScript函数式编程的一些理解

    最近在看JavaScript函数式编程的部分,有些阅读之后的心得和理解,我想总结并分享出来. 首先是什么是函数式编程?函数式编程实际上与过程式编程一样,是一种编程的风格.函数式编程是以函数作为第一公民 ...

  6. JavaScript 函数式编程(三)

    slide 地址 四.Talk is cheap!Show me the ... MONEY! 以下内容主要参考自 Professor Frisby Introduces Composable Fun ...

  7. 我眼中的JavaScript函数式编程

    JavaScript 函数式编程是一个存在了很久的话题,但似乎从 2016 年开始,它变得越来越火热.这可能是因为 ES6 语法对于函数式编程更为友好,也可能是因为诸如 RxJS (ReactiveX ...

  8. JavaScript函数式编程之深入理解纯函数

    更多相关内容见博客 https://github.com/zhuanyongxigua/blog 纯函数是函数式编程的基础,需要重点理解. 纯函数的概念: 纯函数是这样一种函数,即相同的输入,永远会得 ...

  9. SegmentFault 技术周刊 Vol.16 - 浅入浅出 JavaScript 函数式编程

    函数式编程(Functional Programming),一看这个词,简直就是学院派的典范. 以至于从 Lisp 的创世,到 Scheme.Haskell.Clean.Erlang.Miranda. ...

最新文章

  1. 宏观经济学思维导图_巧用思维导图,提升初三化学专题复习课实效
  2. Cifar10与ResNet18实战、lenet5、resnet(学习笔记)
  3. java注解传递value_spring中@value注解需要注意的问题
  4. nio的应用 java_Java NIO 在网络编程中的应用
  5. 前端工程师必备谷歌浏览器F12下的调试知识点
  6. js实现svg图形转存为图片下载
  7. .net函数查询_SQL查询语句总是先执行SELECT?你们都错了!
  8. WPF中Auto与*的区别
  9. Kafka—topic的查询和创建
  10. spark通过合理设置spark.default.parallelism参数提高执行效率
  11. 毕设/私活/必备,一个挣钱的标准开源前后端分离【springboot+vue+redis+Spring Security】脚手架--若依框架
  12. 微信小程序获取二维码:报错47001 data format error
  13. 如何运用js制作简单的登录界面(html)
  14. 为什么用格式刷不能复制行距_如何使用格式刷在PowerPoint中复制格式
  15. Audio解析strategy配置文件
  16. 【CSP-S 2019模拟】题解
  17. 【解决方案】SkeyeVSS综合安防视频云服务在零售连锁巡店中的应用
  18. 小括号、中括号、大括号(正则表达式)
  19. putchar、getchar 大小写转化
  20. C语言system()函数

热门文章

  1. krpano学习——xml代码
  2. 火狐浏览器弹性布局没有开启的问题
  3. js完美转换阿拉伯数字为数字大写
  4. Python 樱花树
  5. 自己开发基于Web的打印控件,真正免费不是共享
  6. 航空网站航班查询自动点击提醒器
  7. 【观察】VxRail领跑超融合市场,揭秘背后的“幕后英雄”
  8. switch为什么总是出现问题?
  9. html中自定义右键菜单功能,HTML中自定义右键菜单功能
  10. 我们且请仁兄到敝庄 水浒