A análise detalhada e o desenvolvimento de functores
Recentemente, estudei no campo de treinamento altamente remunerado do front end da Dragou University. Aprendi muitos conhecimentos que não conhecia antes. Experimentei muitos conceitos e métodos de programação de aprendizagem de programação. Obrigado ao Sr. Cui Cui, a turma e o instrutor
A seguir está um resumo do meu estudo de functores
Deseja processar a string, por exemplo, todos se tornam caracteres maiúsculos e retornam uma nova string
let str = "la gou jiao yu"
let UpperStr = str.toUpperCase()
console.log(UpperStr)
É muito simples. Pode ser facilmente implementado pelo método toUpperCase , mas esse método é para manipular diretamente str, para que possamos envolvê-lo em um contêiner e operar esse contêiner para realizar indiretamente a operação dos dados, vamos operar o functor, deixe o functor operar a própria string, de modo a conseguir manipulação indireta da string, para que possamos nos concentrar no método, independentemente do que seja
Portanto, este contêiner é na verdade um functor
Functores básicos, que é o contêiner mais primitivo
class Container {
constructor (value) {
this._value = value
}
map (fn) {
return new Container(fn(this._value))
}
}
let r = new Container(str).map( x => x.toUpperCase())
console.log(r)
Desta forma, podemos usar o functor para introduzir a string de operação, o que melhora muito a segurança
Em seguida, vamos melhorar este functor. É inconveniente fazer um novo sempre que o chamamos , então vamos usar um método de para otimizar este functor.
Otimização básica do functor
class Container {
static of (value) {
return new Container(value)
}
// 这样一来在函子内部就可以完成 new 的操作 ,不用每一次都new一个新的函子对象出来
constructor (value) {
this._value = value
}
map (fn) {
return Container.of(fn(this._value))
}
}
let str = "la gou jiao yu"
let r = Container.of(str).map( x => x.toUpperCase())
console.log(r)
Nesse processo, você também pode fazer chamadas encadeadas para obter o processamento contínuo de strings
let r = Container.of(str)
.map( x => x.toUpperCase())
.map(x => x.slice(0,6))
.map(x => x.toLowerCase())
console.log(r)
Mas, em muitos casos, a chamada de função não é fácil, é muito provável que o valor passado esteja vazio
MayBe functors
Então temos que determinar se o valor passado está vazio, para que seja possível ter o functor MayBe, o que significa que este functor está lidando com possíveis vagas
// MayBe 函子
class MayBe {
static of (value) {
return new MayBe(value)
}
constructor (value) {
this._value = value
}
map (fn) {
return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
}
isNothing () {
return this._value === null || this._value === undefined
}
}
Começamos a otimizar com base em um functor e mudamos o nome para Maybe, que é o functor Maybe.
let r = MayBe.of(null)
.map( x => x.toUpperCase())
.map(x =>x.slice(0,6))
.map(x => x.toLowerCase())
console.log(r)
// 现在我们的函子拥有了处理空值的能力
Resultado:
qualquer um dos functor
Mas, no processo de chamada, queremos saber qual etapa da chamada tem um problema, então temos o functor Either
O significado de qualquer um em inglês é um dos dois, ou ... ou ..., ou ... ou ...
Portanto, qualquer um dos functor consiste em duas partes
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))
}
}
Mais tarde, para uma melhor compreensão, fiz qualquer um deles parecer mais com um functor
let Either = {
Left ,Right}
// 我把这两个方法存到了一个Either对象里
function parseJSON (str) {
try {
return Either.Left.of(JSON.parse(str))
} catch (e) {
return Either.Right.of({
error: e.message })
}
}
let r = parseJSON('{ "name": "zs" }')
.map(x => x.name.toUpperCase())
console.log(r)
Desta forma, chamando métodos diferentes no objeto Either, o efeito de esquerda ou direita é alcançado
Functores IO
O functor se desenvolveu até este ponto. Uma vez que os dados podem ser processados, os métodos e funções devem ser capazes de processá-los?
Portanto, existem functores IO, usados para lidar indiretamente com as ações da função
// IO 函子
const fp = require('lodash/fp')
// 引入函数式编程的库lodash
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(p => p.execPath)
// console.log(r)
console.log(r._value())
Monad functores
Por se tratar de uma chamada de função, existe alguma relação de aninhamento, por exemplo, o código a seguir, existe o fenômeno de IO (IO (x)), o functor é chamado no functor, que não é conciso
// IO 函子的问题
const fs = require('fs')
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 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
})
}
let cat = fp.flowRight(print, readFile)
// IO(IO(x))
let r = cat('package.json')._value()._value()
console.log(r)
Para resolver este problema, o functor Monad foi introduzido. Monad significa mônada em inglês, e também significa uma entidade indivisível. Assim, os functores Monad são criados para resolver os functores IO acima e atingir o nivelamento.
// IO Monad
const fs = require('fs')
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))
}
join () {
return this._value()
}
flatMap (fn) {
return this.map(fn).join()
}
}
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
})
}
let r = readFile('package.json')
// .map(x => x.toUpperCase())
.map(fp.toUpper)
.flatMap(print)
.join()
console.log(r)
Após resolver o problema de nivelamento, para funções, um dos problemas mais importantes é o problema assíncrono, e então o functor Tarefa é criado para lidar com o problema assíncrono.
// Task 处理异步任务
const fs = require('fs')
const {
task } = require('folktale/concurrency/task')
const {
split, find } = require('lodash/fp')
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)
}
})
Até agora, o functor foi apresentado. Na verdade, a ideia do functor é muito simples, é manipular dados e funções indiretamente, então eles são empacotados em um contêiner, e este contêiner é o functor
Para atender a diferentes necessidades, uma variedade de functores são derivados, então às vezes o foco do aprendizado de programação é descobrir quais problemas estão na base e como resolvê-los. Na verdade, todas as novas tecnologias são constantemente otimizadas. Melhore e resolva alguns problemas subjacentes.
ap(find(x => x.includes('version')))
.run()
.listen({
onRejected: err => {
console.log(err)
},
onResolved: value => {
console.log(value)
}
})
Até agora, o functor foi apresentado. Na verdade, a ideia do functor é muito simples, é manipular dados e funções indiretamente, então eles são empacotados em um contêiner, e este contêiner é o functor
Para atender a diferentes necessidades, uma variedade de functores são derivados, então às vezes o foco do aprendizado de programação é descobrir quais problemas estão na base e como resolvê-los. Na verdade, todas as novas tecnologias são constantemente otimizadas. Melhore e resolva alguns problemas subjacentes.
Em segundo lugar, a ideia de operação indireta também vale a pena aprender. Este método não invadirá o objeto original e é muito seguro. No princípio da vinculação de dados bidirecional do vue, Object.defineProperty é usado para monitorar e operar o objeto, mas após a introdução do proxy, é muito seguro monitorar o objeto original monitorando o objeto instanciado pelo proxy.É uma idéia de programação muito importante não manipular diretamente os dados originais.