fp-ts 入门:IO
错误处理
起重
在fp-ts
同步中,有效计算由类型表示IO
,该类型基本上是一个thunk,即具有以下签名的函数:() => A
interface IO<A> {
(): A
}
请注意,表示永不失败的IO
计算。
此类计算的示例如下:
- 读/写
localStorage
- 获取当前时间
- 写信给
console
- 获取随机数
示例(读取/写入localStorage
)
import { fromNullable, Option } from 'fp-ts/Option'
const getItem = (key: string): IO<Option<string>> => () =>
fromNullable(localStorage.getItem(key))
const setItem = (key: string, value: string): IO<void> => () =>
localStorage.setItem(key, value)
示例(获取当前时间)
const now: IO<number> = () => new Date().getTime()
示例(写入console
)
const log = (s: unknown): IO<void> => () => console.log(s)
示例(获取随机数)
const random: IO<number> = () => Math.random()
该IO
类型允许一个Monad实例,因此您可以map
......
import { io } from 'fp-ts/IO'
/** get a random boolean */
const randomBool: IO<boolean> = io.map(random, n => n < 0.5)
...或chain
计算
/** write to the `console` a random boolean */
const program: IO<void> = io.chain(randomBool, log)
program()
请注意,在您调用之前什么也不会发生program()
。
这是因为它program
是一个仅代表有效计算的值,因此为了执行任何副作用,您必须“运行该操作”。IO
由于IO
动作只是值,因此您可以使用Monoid等有用的抽象来处理它们......
示例(龙与地下城)
import { log } from 'fp-ts/Console'
import { getMonoid, IO, io } from 'fp-ts/IO'
import { fold, Monoid, monoidSum } from 'fp-ts/Monoid'
import { randomInt } from 'fp-ts/Random'
type Die = IO<number>
const monoidDie: Monoid<Die> = getMonoid(monoidSum)
/** returns the sum of the roll of the dice */
const roll: (dice: Array<Die>) => IO<number> = fold(monoidDie)
const D4: Die = randomInt(1, 4)
const D10: Die = randomInt(1, 10)
const D20: Die = randomInt(1, 20)
const dice = [D4, D10, D20]
io.chain(roll(dice), result => log(`Result is: ${result}`))()
/*
Result is: 11
*/
..或者定义有用的组合器
/** Log any value to the console for debugging purposes */
const withLogging = <A>(action: IO<A>): IO<A> =>
io.chain(action, a => io.map(log(`Value is: ${a}`), () => a))
io.chain(roll(dice.map(withLogging)), result => log(`Result is: ${result}`))()
/*
Value is: 4
Value is: 2
Value is: 13
Result is: 19
*/
错误处理
如果我们想要表示一个可能失败的同步有效计算怎么办?
我们需要两个效果:
类型构造函数 | 效果(解释) |
---|---|
IO<A> |
同步有效计算 |
Either<E, A> |
计算可能失败 |
解决方案是放入Either
里面IO
,这导致了IOEither
类型
interface IOEither<E, A> extends IO<Either<E, A>> {}
当我们“运行”一个类型的值时IOEither<E, A>
,如果得到一个Left
,则表示计算失败,错误类型为E
,否则我们得到一个Right
,表示计算成功,并且值为类型A
。
示例(读取文件)
由于fs.readFileSync
可能会抛出,我将使用tryCatch
助手
tryCatch: <E, A>(f: () => A) => IOEither<E, A>
其中f
是一个 thunk,它要么抛出错误(由 自动捕获tryCatch
),要么返回 类型的值A
。
import { toError } from 'fp-ts/Either'
import { IOEither, tryCatch } from 'fp-ts/IOEither'
import * as fs from 'fs'
const readFileSync = (path: string): IOEither<Error, string> =>
tryCatch(() => fs.readFileSync(path, 'utf8'), toError)
readFileSync('foo')() // => left(Error: ENOENT: no such file or directory, open 'foo')
readFileSync(__filename)() // => right(...)
起重
该fp-ts/IOEither
模块提供了其他允许创建类型值的帮助程序IOEither
,它们统称为提升函数。
以下是摘要
起始值 | 升降功能 |
---|---|
IO<E> |
leftIO: <E, A>(ml: IO<E>) => IOEither<E, A> |
E |
left: <E, A>(e: E) => IOEither<E, A> |
Either<E, A> |
fromEither: <E, A>(ma: Either<E, A>) => IOEither<E, A> |
A |
right: <E, A>(a: A) => IOEither<E, A> |
IO<A> |
rightIO: <E, A>(ma: IO<A>) => IOEither<E, A> |
示例(加载随机文件)
假设我们要随机加载三个文件(1.txt
、、)中的一个文件的内容2.txt
。3.txt
该randomInt: (low: number, high: number) => IO<number>
函数返回一个在闭区间内均匀分布的随机整数[low, high]
import { randomInt } from 'fp-ts/Random'
我们可以randomInt
用readFileSync
上面定义的函数进行链接
import { ioEither } from 'fp-ts/IOEither'
const randomFile = ioEither.chain(
randomInt(1, 3), // static error
n => readFileSync(`${__dirname}/${n}.txt`)
)
但这不进行类型检查!
类型不一致:在上下文randomInt
中运行,IO
而在上下文readFileSync
中运行IOEither
。
然而,我们可以通过使用(参见上面的摘要)randomInt
来提升上下文IOEither
rightIO
import { ioEither, rightIO } from 'fp-ts/IOEither'
const randomFile = ioEither.chain(rightIO(randomInt(1, 3)), n =>
readFileSync(`${__dirname}/${n}.txt`)
)