如何在 FP 中管理 monad,特别是在 fp-ts 中

How to manage monads in FP and specially in fp-ts

我对函数式编程非常陌生,特别是 fp-ts 库。

我的问题包括两部分:

  1. 我看到了一种将 Monad 从一种类型转变为另一种类型的模式,例如从 Task 转变为 IO 或相反,我们如何管理它,我们应该始终保持一种还是应该我们随着链条的继续而改变?
  2. 如何简单地使打字稿跟随这些类型从一个到另一个的变化?

例如,假设我们有几个函数,我们想将它们组合在一起,如下所示,我知道这个例子可能不是很实用,但它可以达到目的。

declare function getRnd(min: number, max: number): IO<number>; // Returns a random number within the range
declare function getPage(pageNo: number): TaskEither<Error, string>; // Make an Http request
declare function getLinks(pageContent: string): Option<string[]>; // Returns some links

// Let's say we wanna get a random page number and then return the links on it
// How do we compose these functions?
const getPageLinks = pipe(
  getRnd(2, 4),
  IO.chain(getPage), // I'm pretty sure TS will yells at me here
  TaskEither.chain(getLinks),
  log, // ?
)

1.) turning Monads from one type to another, how do we manage this, should we stay always on one or should we change as the chain continues?

你想要某种 (natural) transformationIO 切换到 Task/TaskEither。反过来对我来说没有意义,因为异步效果不能转换为同步效果。

chainpreserve the structure 。所以 IO.chain(getPage) 中的 getPage 需要一个签名 number -> IO<whatever>。您可以改为使用 map 添加额外的嵌套层,例如:

pipe(getRnd(2, 4), IO.map(getPage)); // I.IO<TE.TaskEither<Error, string>>

总的来说,方法没有对错之分,只看目的。请注意,嵌套的数据类型越多,处理内部值就越复杂。使用代数结构的函数式编程的一部分是为了避免在源头上不必要的嵌套。

在你的情况下,将所有东西合并成一个统一的 TaskEither 确实有意义 - 使用类型 IO<TaskEither<...>>TaskEither<...>.[=32 相比,你不会有任何优势=]

2.) How to simply make typescript follow of these type changes going from one to another?

您可以使用TaskEither.rightIO to transform IO to TaskEither (CodeSandbox):

import { taskEither as TE, io as I, option as O, pipeable as P, either as E } from "fp-ts";

declare function log<T>(t: T): I.IO<T>;

const getPageLinks = P.pipe(
  getRnd(2, 4),
  TE.rightIO,
  TE.chain(getPage),
  TE.map(getLinks),
  TE.chain(v => TE.rightIO(log(v)))
); // TE.TaskEither<Error, O.Option<string[]>>

这个也可以,因为 TS 使用结构类型(但我会推荐前者):

const getPageLinks2 = P.pipe(
  getRnd(2, 4),
  I.chain(getPage), // this also works
  I.map(v => v.then(vv => E.either.map(vv, getLinks))),
  I.chain(log)
); // I.IO<Promise<E.Either<Error, O.Option<string[]>>>>