返回

fp-ts 丛谈之 Reader 详解

前端

Reader 概述

Reader 在 fp-ts 中,Reader 的定义如下:

export interface Reader<E, A> {
  (e: E): A;
}

也就是一个类型为 r -> a 的函数,r 可以看作计算所需的环境,而 a 是计算的结果。它经常被用来做依赖注入。

先来看一段代码:

const ask = <E, A>(e: E) => () => e;

// 使用场景一
const env = { user: "Alice" };

// 获取当前环境中的用户变量
const getUser = ask<typeof env, string>();
console.log(getUser(env)); // "Alice"

在这个例子中,ask 函数创建了一个 Reader,它将环境作为参数,并返回环境中的 user 变量。然后,我们使用 getUser Reader 来获取当前环境中的用户变量。

Reader 的使用场景

Reader 经常被用来做依赖注入。依赖注入是一种设计模式,它允许我们通过参数的形式将依赖关系注入到对象中。这使得我们可以很容易地测试对象,而不用担心它是否能够访问它的依赖关系。

例如,我们有一个函数 foo,它需要一个 UserService 对象作为参数:

function foo(userService: UserService): void {
  // ...
}

如果我们想测试 foo 函数,我们就需要创建一个 UserService 对象并把它传递给 foo 函数。这可能会很麻烦,尤其是当 UserService 对象需要很多依赖关系的时候。

我们可以使用 Reader 来解决这个问题。我们可以创建一个 UserServiceReader Reader,它将 UserService 对象作为参数,并返回一个 foo 函数:

const UserServiceReader = (userService: UserService) => () => foo(userService);

然后,我们就可以使用 UserServiceReader Reader 来测试 foo 函数:

const userService = new UserService();
const fooReader = UserServiceReader(userService);
fooReader(); // ...

这样,我们就不需要创建 UserService 对象并把它传递给 foo 函数了。我们只需要创建一个 UserServiceReader Reader,然后使用它来调用 foo 函数即可。

Reader 的组合

Reader 可以组合在一起形成新的 Reader。这可以通过使用 chain 方法来实现。chain 方法接受一个函数作为参数,该函数返回一个新的 Reader。chain 方法将把当前 Reader 的结果作为参数传递给新 Reader。

例如,我们有一个 UserServiceReader Reader,它将 UserService 对象作为参数,并返回一个 foo 函数:

const UserServiceReader = (userService: UserService) => () => foo(userService);

我们还可以创建一个 UserRepositoryReader Reader,它将 UserRepository 对象作为参数,并返回一个 getUser 函数:

const UserRepositoryReader = (userRepository: UserRepository) => () => getUser(userRepository);

我们可以使用 chain 方法将这两个 Reader 组合在一起,创建一个新的 Reader:

const combinedReader = UserServiceReader.chain(UserRepositoryReader);

这个新的 Reader 将 UserService 对象和 UserRepository 对象作为参数,并返回一个 foo 函数和一个 getUser 函数。

我们可以使用 combinedReader Reader 来测试 foo 函数和 getUser 函数:

const userService = new UserService();
const userRepository = new UserRepository();
const combinedReader = UserServiceReader(userService).chain(UserRepositoryReader(userRepository));
const foo = combinedReader();
const user = foo.getUser();

这样,我们就不需要创建 UserService 对象和 UserRepository 对象并把它们传递给 foo 函数和 getUser 函数了。我们只需要创建一个 combinedReader Reader,然后使用它来调用 foo 函数和 getUser 函数即可。

结语

Reader 是 fp-ts 中一个非常有用的工具。它可以用来做依赖注入,也可以用来组合不同的函数。这使得我们可以编写出更灵活、更易于测试的代码。