TypeScript 中函数式编程的相互依赖操作
Interdependent operations with functional programming in TypeScript
我正在开发一个使用 fp-ts and io-ts 以函数式 TypeScript 编写的应用程序。我需要检索一组 JSON 个配置文件。其中一些 JSON 文件包含检索其他一些 JSON 文件所需的信息。我想知道对于这种依赖关系什么是好的抽象。
我目前发现自己正在编写定义硬编码阶段的代码(请参阅下面的伪代码)。这种方法的问题在于,这些阶段的名称完全没有意义,表达的是技术细节,而不是代码的预期行为。
type Stage1 = { config1: Config1 }
const stage1 = (): TaskEither<Err, Stage1> => // ...
type Stage2 = Stage1 & { config2: Config2, config3: Config3 }
const stage2 = (s1: Stage1): TaskEither<Err, Stage2> => // ...
type Stage3 = Stage2 & { config4: Config4 }
const stage3 = (s2: Stage2): TaskEither<Err, Stage3> => // ...
const complete = pipe(
stage1(),
chain(stage2),
chain(stage3),
)
我编写了以下帮助函数 wheel
解决了这个问题。剩下的问题是这是否是一项新发明。也许这种编程模式有一个名字。也许它已经以某种形式成为 fp-ts 的一部分。
import * as P from 'maasglobal-prelude-ts';
const wheel = <I extends {}, O extends {}, E>(cb: (i: I) => P.TaskEither<E, O>) => (
ei: P.TaskEither<E, I>,
): P.TaskEither<E, I & O> =>
P.pipe(
ei,
P.TaskEither_.chain((i) =>
P.pipe(
cb(i),
P.TaskEither_.map((o) => ({ ...i, ...o })),
),
),
);
const complete = P.pipe(
P.TaskEither_.right({}),
wheel(() => P.TaskEither_.right({ foo: 123 })),
wheel(() => P.TaskEither_.right({ bar: 456 })),
wheel(({ foo }) => P.TaskEither_.right({ quux: 2 * foo })),
wheel(({ quux, bar }) => P.TaskEither_.right({ quuxbar: quux + '-' + bar })),
);
expect(complete).toStrictEqual({ foo: 123, bar: 456, quux: 246, quuxbar: '246-456' });
看起来很像您在其他语言中使用 do 表示法或 for comprehension 所做的事情,或者在 fp-ts 中使用 Do
:
import { Do } from 'fp-ts-contrib/lib/Do';
import { taskEither, right } from 'fp-ts/lib/TaskEither';
const complete = Do(taskEither)
.bind('foo', right(123))
.bind('bar', right(456))
.bindL('quux', ({ foo }) => right(2 * foo))
.return(({ quux, bar }) => right({ quuxbar: quux + '-' + bar }));
我正在开发一个使用 fp-ts and io-ts 以函数式 TypeScript 编写的应用程序。我需要检索一组 JSON 个配置文件。其中一些 JSON 文件包含检索其他一些 JSON 文件所需的信息。我想知道对于这种依赖关系什么是好的抽象。
我目前发现自己正在编写定义硬编码阶段的代码(请参阅下面的伪代码)。这种方法的问题在于,这些阶段的名称完全没有意义,表达的是技术细节,而不是代码的预期行为。
type Stage1 = { config1: Config1 }
const stage1 = (): TaskEither<Err, Stage1> => // ...
type Stage2 = Stage1 & { config2: Config2, config3: Config3 }
const stage2 = (s1: Stage1): TaskEither<Err, Stage2> => // ...
type Stage3 = Stage2 & { config4: Config4 }
const stage3 = (s2: Stage2): TaskEither<Err, Stage3> => // ...
const complete = pipe(
stage1(),
chain(stage2),
chain(stage3),
)
我编写了以下帮助函数 wheel
解决了这个问题。剩下的问题是这是否是一项新发明。也许这种编程模式有一个名字。也许它已经以某种形式成为 fp-ts 的一部分。
import * as P from 'maasglobal-prelude-ts';
const wheel = <I extends {}, O extends {}, E>(cb: (i: I) => P.TaskEither<E, O>) => (
ei: P.TaskEither<E, I>,
): P.TaskEither<E, I & O> =>
P.pipe(
ei,
P.TaskEither_.chain((i) =>
P.pipe(
cb(i),
P.TaskEither_.map((o) => ({ ...i, ...o })),
),
),
);
const complete = P.pipe(
P.TaskEither_.right({}),
wheel(() => P.TaskEither_.right({ foo: 123 })),
wheel(() => P.TaskEither_.right({ bar: 456 })),
wheel(({ foo }) => P.TaskEither_.right({ quux: 2 * foo })),
wheel(({ quux, bar }) => P.TaskEither_.right({ quuxbar: quux + '-' + bar })),
);
expect(complete).toStrictEqual({ foo: 123, bar: 456, quux: 246, quuxbar: '246-456' });
看起来很像您在其他语言中使用 do 表示法或 for comprehension 所做的事情,或者在 fp-ts 中使用 Do
:
import { Do } from 'fp-ts-contrib/lib/Do';
import { taskEither, right } from 'fp-ts/lib/TaskEither';
const complete = Do(taskEither)
.bind('foo', right(123))
.bind('bar', right(456))
.bindL('quux', ({ foo }) => right(2 * foo))
.return(({ quux, bar }) => right({ quuxbar: quux + '-' + bar }));