JavaScript 中的基本 monad
我将解释一些常见的 monad,你现在可以在 JavaScript 中使用它们。monad 可以帮助你的代码更易于阅读、更易于维护,最重要的是——更安全。
或许
Maybe monad 用于处理可空数据。我们经常在 JavaScript 中处理数据,例如格式化、计算、过滤和排序。但我们通常需要在执行任何操作之前确保数据存在。这时 Maybe 就能派上用场了。
我将使用一个名为Pratica的小型友好辅助库来提供本文中 monad 的实现。
让我们看一看可以从 Maybe monad 中受益的片段。
const data = 'Hello my name is Jason'
if (data) {
console.log(data.toUpperCase()) // HELLO MY NAME IS JASON
}
现在让我们看看如何使用 Maybe 来重构它。
import { Maybe } from 'pratica'
Maybe('Hello my name is Jason')
.map(data => data.toUpperCase())
.cata({
Just: data => console.log(data), // HELLO MY NAME IS JASON
Nothing: () => console.log('No data available')
})
瞧,我们不需要检查数据是否存在,因为如果数据为空,Maybe 会自动停止执行任何函数。避免类似这样的错误Uncaught TypeError: Cannot read property 'toUpperCase' of undefined
现在你可能不会立刻看到它的优势,但这并不是 Maybe 的亮点所在。让我们来看另一个步骤更多的例子。
// Step 1: Filter cool people
// Step 2: Find the first cool person
// Step 3: Log their uppercased name if there is one
const data = [
{ name: 'Jason', level: 7, cool: true },
{ name: 'Blanche', level: 8, cool: false }
]
if (data) {
const coolPeople = data.filter(person => person.cool)
if (coolPeople) {
const firstCoolPerson = coolPeople[0]
if (firstCoolPerson && firstCoolPerson.name) {
console.log(firstCoolPerson.name.toUpperCase()) // JASON
}
}
}
现在让我们看看 Maybe 的替代方案。
import { Maybe } from 'pratica'
Maybe(data)
.map(people => people.filter(person => person.cool))
.map(people => people[0])
.map(person => person.name)
.map(name => name.toUpperCase())
.cata({
Just: data => console.log(data), // JASON
Nothing: () => console.log('No data available')
})
如果数据实际上为空或未定义,则所有 .map 函数都不会运行,并且会在 cata 中执行 Nothing 函数。
但是,如果数据为空,我们还想返回一个默认值。那么我们可以使用此.default()
方法。
import { Maybe } from 'pratica'
Maybe(null)
.map(people => people.filter(person => person.cool))
.map(people => people[0])
.map(person => person.name)
.map(name => name.toUpperCase())
.default(() => 'No cool people yo')
.cata({
Just: data => console.log(data), // No cool people yo
Nothing: () => console.log('No data available')
})
哇,这么干净,这么平坦。
结果
因此,我们了解到 Maybe monad 非常适合处理可空数据,但是如果我们想检查数据的值并根据值执行不同的操作,该怎么办?
输入结果 monad(或有时称为 Either monad)。
Result 用于“分支”你的逻辑。我们先来看一个没有 Result 的例子。
const person = { name: 'Jason', level: 7, cool: true }
if (person.level === 7) {
console.log('This person is level 7, ew')
} else {
console.error('This person is some other level, but not 7')
}
好的,现在有结果了。
import { Ok, Err } from 'pratica'
const person = { name: 'Jason', level: 7, cool: true }
const lvl = person.level === 7
? Ok('This person is level 7, ew')
: Err('This person is some other level, but not 7')
lvl.cata({
Ok: msg => console.log(msg), // This person is level 7, ew
Err: err => console.log(err) // This person is some other level, but not 7
})
嗯,我不明白这有什么意义。Ok 和 Err 是什么?这有什么好处吗?
在解释之前我们先再举一个例子。
在这个例子中,我们需要在继续之前验证一些数据。
const data = {
first: 'Jason',
level: 85,
cool: true,
shirt: {
size: 'm',
color: 'blue',
length: 90,
logo: {
color1: '#abc123',
color2: '#somehexcolor'
}
}
}
if (data) {
if (data.shirt) {
if (data.shirt.logo) {
if (data.shirt.logo.color1 !== 'black') {
// Color1 is valid, now lets continue
console.log(data.shirt.logo.color1)
} else {
console.error ('Color1 is black')
}
} else {
console.error ('No logo')
}
} else {
console.error ('No shirt')
}
} else {
console.error ('No data')
}
看起来有点乱。我们看看如何使用 Result 来改进。
import { Ok, Err } from 'pratica'
const hasData = data => data
? Ok (data.shirt)
: Err ('No data')
const hasShirt = shirt => shirt
? Ok (shirt.logo)
: Err ('No shirt')
const hasLogo = logo => logo
? Ok (logo.color1)
: Err ('No logo')
const isNotBlack = color => color !== 'black'
? Ok (color)
: Err ('Color is black')
hasData (data2)
.chain (hasShirt)
.chain (hasLogo)
.chain (isNotBlack)
.cata ({
Ok: color => console.log(color), // #abc123
Err: msg => console.log(msg)
})
有趣的是,它平坦了很多,但我仍然不明白发生了什么。
好的,事情是这样的。
我们从 hasData 函数开始。它接受需要验证的初始数据,并返回下一个需要验证的数据,但返回的数据被包装在 Result monad 中,更具体地说,是 Ok 或 Err 类型。这两个类型构成了 Result monad,也是我们的应用程序分支逻辑的方式。
.chain()
为什么每一行都有这个?
每个函数都会返回 Ok 或 Err 数据类型。但每个函数也期望其输入仅仅是数据,而不是包装在 monad 中的数据。因此,在每个函数上调用 chain 会将数据从 monad 中解包出来,以便函数能够读取其中的内容。
为什么这样更好?
好吧,更好是主观的,但在函数式编程中,这被认为是更好的,因为它将IO(IO是控制台日志语句)推到了程序的边缘。这意味着有更多纯函数可以进行单元测试,并且内部不会混入IO。纯函数中包含IO并不再使它们变得纯粹,这意味着它们会更难进行单元测试,并且会成为bug的根源。控制台日志在JavaScript中并不是什么大问题,但如果IO是发出网络请求,那么这种编程方式就会有很大的不同,因为所有逻辑/验证都将独立于IO,并且更易于测试和维护。
因此,您今天就可以开始使用这两个流行的 monad。
这是我的第一篇 dev.to 文章,请在评论中告诉我您的想法!
如果您想了解有关 monad 的更多信息,请查看这些很酷的文章和库。
鏂囩珷鏉ユ簮锛�https://dev.to/rametta/basic-monads-in-javascript-3el3