聊一聊函数式编程的函子是干什么的,字节大神强推千页PDF学习笔记

  • 异常会让函数变得不纯,Either 函子可以用来做异常处理

  • 示例

const { truncate } = require(“fs”)

class Left {

static of (value) {

return new Left(value)

}

constructor (value) {

this._value = value

}

map (fn) {

return this

}

}

class Right {

static of (value) {

return new Right(value)

}

constructor (value) {

this._value = value

}

map (fn) {

return Right.of(fn(this._value))

}

}

// let r1 = Right.of(12).map(x => x + 2)

// let r2 = Left.of(12).map(x => x + 2)

// console.log(r1) // Right { _value: 14 }

// console.log(r2) // Left { _value: 12 }

/**

  • 我们需要先定义两个类,两个类之间有区别,主要是Map方法中有所区别

  • 我们打印后的两个输出的结果是不一样的,两个代码都是一样的,只是一个使用left创建,一个试用right创建

  • right中的值 +2 返回了14,但是 left 中直接是返回了传入的数据,没有做任何处理

  • 我们可以对比两个类中的map方法

  • Left 中 直接把当前对象返回了,并没有调用我们传入的fn,这样做是为了让Left中嵌入一个错误消息

  • 下边我们来掩饰一个可能会发生错误的函数,比如我们要把一个json形式的字符串,转成一个json对象

  • */

function parseJSON (str) {

try{

return Right.of(JSON.parse(str))

} catch (e) {

return Left.of({error : e.message})

}

}

// let r1 = parseJSON(‘{ name: zs }’)

// console.log(r1) // Left { _value: { error: ‘Unexpected token n in JSON at position 2’ } }

// let r2 = parseJSON(‘{ “name”: “zs” }’)

// console.log(r2) // Right { _value: { name: ‘zs’ } }

let r3 = parseJSON(‘{ “name”: “zs” }’)

.map(x => x.name.toUpperCase())

console.log(r3) // Right { _value: ‘ZS’ }

/**

  • 我们先声明一个叫做 parseJSON 的函数,并给他接收一个参数

  • 接下来我们要调用JSON.parse把我们传入的字符串转为json对象并且返回

  • 因为调用JSON.parse时可能会出现异常,所以我们使用 try {} catch(e) {}

  • 因为出现错误情况我们不去处理的话,那就不是一个纯函数

  • 现在我们希望用函数式的方式去处理,所以我们需要写一个纯函数

  • 我们现在要在try里return一个函子,我们会把我们转换后的结果交给这个函子,将来在这个函子内部去处理

  • 如果出现错误我们是不能不管的,我们也要返回一个值,因为对于纯函数来说,对于相同的输入,始终要有相同的输出

  • 那这个时候我们也要返回一个函子,因为我们Either中有Left和Right,我们用right去处理正确的值

  • 如果出现异常的时候我们可以返回一个left中的函子,而left这个函子里边,可以帮我们去存储一些错误的信息

  • */

IO 函子

===============================================================

  • 现在我们已经对函子有一个简单的认识,我们可以把它想象为一个盒子,盒子里保存一个值,然后通过调用盒子的map方法,我们可以传入一个函数,通过这个函数,对盒子里的值进行处理,现在我们学习一个新的函子,

  • IO 函子中的 _value 是一个函数,因为函数是一等公民,所以这里是把函数作为值来处理

  • IO 函子可以把不纯的动作存储到 _value中,_value中存储的是函数,我们在函子内部并没有调用这个函数,所以通过IO函数我们其实是延迟执行这个不纯的操作(惰性执行)包装当前的操作是一个纯的操作

  • 把不纯的操作交给调用者来处理

  • 示例

const fp = require(‘lodash/fp’)

class Io {

static of (value) {

return new Io(function () {

return value

})

}

constructor (fn) {

this._value = fn

}

map (fn) {

return new Io(fp.flowRight(fn,this._value))

}

}

let r = Io.of(process).map( x => x.execPath)

console.log(r._value()) // C:\Program Files\nodejs\node.exe

/**

  • constructor要去接收一个函数,因为IO函子里边保存的是函数,我们将接收到的函数保存在 _value 中

  • of方法与之前有点差异,他要接受一个数据,然后返回一个新的IO函子

  • 在这里便要返回一个IO函子,所以要调用IO构造函数,然后传入一个函数

  • 因为刚刚我们写过它的构造函数,它的构造函数需要接收的是一个函数

  • 所以在这里我们传入一个函数,在这个函数内,我们把刚刚of方法的值返回

  • 到这里我们其实可以感受到IO函子,最终想要的还是一个结果,只不过他把取值的过程包装到了函数里边来

  • 将来需要值的时候,在来执行这个函数,来取值

  • map方法和之前一样还是需要传递一个fn,这个方法里边我们还是要返回一个IO函子,这里我们要调用IO的构造函数

  • 而不是调用of方法,因为外部方法里面,我们把当前函子的value,也就是这个函数和我们传入的这个函数

  • 组合成一个新的函数,而不是调用函数去给值 — 这就是和以前不一样的地方

  • */

  • 总结

  • IO函子内部帮我们包装了一些函数,当我们在传递函数的时候,有可能这个函数是一个不纯的操作,但我们不管他是纯还是不纯,我们IO这个函子在执行中,他返回的结果始终是一个纯的操作,IO中有可能包裹不纯的操作,但是当前这个执行始终是一个纯的操作,我们调用map方法时始终会返回一个IO的函子,但是我们IO函子当中的_value属性,它里边保存的这些函数,因为它里边最终要合并很多函数,所以它里边有可能是不纯的,我们把这些不纯的操作,延迟到了调用的时候,我们通过IO函子控制了副作用在可控范围内发生

folktale

==================================================================

Task异步执行


函子可以帮我们控制副作用,进行异常处理,还可以帮我们处理异步任务,因为在异步操作中,会出现回调地狱而使用 Task 函子,会避免回调地狱。

  • 异步任务的实现过于复杂,我们是用 folktale 中的 Task 来演示

  • folktale 一个标准的函数式编程库

  • 和lodash、ramda 不同的是,他没有提供很多功能函数

  • 只提供了一些函数处理的操作,例如:compose、curry等,一些函子Task、Either、MayBe 等

const { compose, curry } = require(‘folktale/core/lambda’)

const { toUpper, first } = require(‘lodash/fp’)

// 这里的柯里化,与lodash中的柯里化,稍微有一点点的区别

let f = curry(2, (x, y) => x + y)

/**

  • 两个参数:

  •  第一个:指明我们后边这个函数他有几个参数,目的是为了避免一些错误
    
  • */

console.log(f(1, 2))

console.log(f(1)(2))

console.log(f()(1, 2))

// compose — 函数组合 — 和lodash中的函数组合 flowRight 不一样

let f = compose(toUpper, first)

console.log(f([‘one’, ‘two’]))

Task 函子


  • folktale(2.3.2)2.x 中的Task 和 1.0 中的 Task 区别很大, 1.0 中的用法更接近我们现在掩饰的函子

  • 这里以2.3.2来演示

  • 示例

// 读取package.json,并将其中的version解析出来

const { task } = require(‘folktale/concurrency/task’) // 导入task函子,路径可查官网

const fs = require(‘fs’) // 导入fs模块

const { split, find } = require(‘lodash/fp’) // 导入lodash/fp的方法

// 读取文件的函数,需要一个参数 — 文件的路径,相对路径可以直接写文件名’package.json’

function readFile ( filename ) {

/**

  • 返回一个task函子,需要接受一个函数,函数的参数是固定的resolver,可以通过官网来查询

  • resolver是一个对象,里边提供了两个方法:resolve() 和 reject() — 类似于promise

  • */

return task(resolver => {

// 异步读取文件,需要三个参数:1—读取的路径;2—使用的编码;3—回调函数(在node中是错误优先);

fs.readFile(filename, ‘utf-8’, (err, data) => {

// 判断读取文件时是否出错

if(err){

// 失败时调用

resolver.reject(err)

}

// 成功时调用

resolver.resolve(data)

})

})

}

// 传入路径

readFile(‘package.json’)

/**

  • 执行完readFile他不会去读取文件,而是返回了一个Task的函子

  • 我们想要它读取文件的话需要调用Task给我们提供的.run方法

  • */

.run()

/**

  • 读取完文件,我们没有传递resolve,我们不知道如何去处理这个数据

  • 所以我们需要Task给我们提供的listen来监听当前的执行状态,这里是以时间的方式来给我们提供的

  • */

.listen({

// 失败时执行的

onRejecter: err => {

console.log(err)

},

// 成功是执行的

onResolved: value =>{

console.log(value)

}

})

// 以换行为切割,分成数组,然后再去其中寻找version

readFile(‘package.json’)

.map(split(‘\n’))

.map(find(x => x.includes(‘version’)))

.run()

.listen({

onRejecter: err => {

console.log(err)

},

onResolved: value =>{

console.log(value)

}

})

Pointed函子

===================================================================

  • Pointed函子时实现了of静态方法的函子

  • of方法是为了避免使用new来创建对象,更深层的含义是of方法用来把值放到上下文Context(把值放到容器中,使用Map来处理值)

  • 示例

class Content {

static of (value) {

return new Content(value)

}

}

let c = Content.of(2)

.map(x => x + 3)

console.log©

IO函子的问题

=================================================================

  • 在使用IO函子的时候,如果我们写出如下代码

const fp = require(‘lodash/fp’)

const fs = require(‘fs’)

class IO {

static of (value) {

return new IO(function () {

return value

})

}

constructor (fn) {

this._value = fn

}

map (fn) {

return new IO(fp.flowRight(fn, this._value))

}

}

let readFile = function (filename) {

return new IO(function () {

return fs.readFileSync(filename, ‘utf-8’)

})

}

let print = function (x) {

return new IO (function () {

console.log(x)

return x

})

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
img

总结

秋招即将开始,校招的朋友普遍是缺少项目经历的,所以底层逻辑,基础知识要掌握好!

而一般的社招,更是神仙打架。特别强调,项目经历不可忽视;几乎简历上提到的项目都会被刨根问底,所以项目应用的技术要熟练,底层原理必须清楚。

这里给大家提供一份汇集各大厂面试高频核心考点前端学习资料。涵盖 HTML,CSS,JavaScript,HTTP,TCP协议,浏览器,Vue框架,算法等高频考点238道(含答案)

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

资料截图 :

高级前端工程师必备资料包

**
[外链图片转存中…(img-H9PHspxX-1711189855959)]

总结

秋招即将开始,校招的朋友普遍是缺少项目经历的,所以底层逻辑,基础知识要掌握好!

而一般的社招,更是神仙打架。特别强调,项目经历不可忽视;几乎简历上提到的项目都会被刨根问底,所以项目应用的技术要熟练,底层原理必须清楚。

这里给大家提供一份汇集各大厂面试高频核心考点前端学习资料。涵盖 HTML,CSS,JavaScript,HTTP,TCP协议,浏览器,Vue框架,算法等高频考点238道(含答案)

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

资料截图 :

高级前端工程师必备资料包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值