如何在 FP 中管理 monad,特别是在 fp-ts 中
How to manage monads in FP and specially in fp-ts
我对函数式编程非常陌生,特别是 fp-ts
库。
我的问题包括两部分:
- 我看到了一种将 Monad 从一种类型转变为另一种类型的模式,例如从
Task
转变为 IO
或相反,我们如何管理它,我们应该始终保持一种还是应该我们随着链条的继续而改变?
- 如何简单地使打字稿跟随这些类型从一个到另一个的变化?
例如,假设我们有几个函数,我们想将它们组合在一起,如下所示,我知道这个例子可能不是很实用,但它可以达到目的。
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) transformation 从 IO
切换到 Task
/TaskEither
。反过来对我来说没有意义,因为异步效果不能转换为同步效果。
chain
将 preserve 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[]>>>>
我对函数式编程非常陌生,特别是 fp-ts
库。
我的问题包括两部分:
- 我看到了一种将 Monad 从一种类型转变为另一种类型的模式,例如从
Task
转变为IO
或相反,我们如何管理它,我们应该始终保持一种还是应该我们随着链条的继续而改变? - 如何简单地使打字稿跟随这些类型从一个到另一个的变化?
例如,假设我们有几个函数,我们想将它们组合在一起,如下所示,我知道这个例子可能不是很实用,但它可以达到目的。
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) transformation 从 IO
切换到 Task
/TaskEither
。反过来对我来说没有意义,因为异步效果不能转换为同步效果。
chain
将 preserve 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[]>>>>