简体   繁体   English

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

[英]How to manage monads in FP and specially in fp-ts

I'm very new to functional programming and specially fp-ts library.我对函数式编程和特别是fp-ts库很fp-ts

My question includes two parts:我的问题包括两部分:

  1. I'm seeing a pattern of turning Monads from one type to another like from Task to IO or vice versa, how do we manage this, should we stay always on one or should we change as the chain continues?我看到了一种将 Monads 从一种类型转换为另一种类型的模式,例如从TaskIO ,反之亦然,我们如何管理这种情况,我们应该始终保持在一个类型上,还是应该随着链的继续而改变?
  2. How to simply make typescript follow of these type changes going from one to another?如何简单地使打字稿遵循这些从一个到另一个的类型更改?

For example let's say we have a couple of functions and we wanna compose them together as below, I know that probably this example is not very practical but it serves the purpose.例如,假设我们有几个函数,我们想将它们组合在一起,如下所示,我知道这个例子可能不太实用,但它可以达到目的。

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? 1.) 将 Monad 从一种类型转变为另一种类型,我们如何管理这一点,我们应该始终保持在一种类型上,还是应该随着链的继续而改变?

You want some kind of (natural) transformation to switch from IO to Task / TaskEither .您想要某种(自然)转换IO切换到Task / TaskEither The other way round doesn't make sense to me, as an async effect cannot be converted to a sync one.反过来对我来说没有意义,因为异步效果无法转换为同步效果。

chain will preserve the structure . chain保留结构 So getPage in IO.chain(getPage) needs a signature number -> IO<whatever> .所以getPage IO.chain(getPage)中的IO.chain(getPage)需要一个签名number -> IO<whatever> You can instead use map to add an additional layer of nesting, like:您可以改为使用map添加额外的嵌套层,例如:

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

In general, there is no right or wrong way, it just depends on the purpose.一般来说,方法没有对错之分,只看目的。 Note, the more nested data types, the more complex it will become to process the inner value(s).请注意,嵌套的数据类型越多,处理内部值就越复杂。 Part of functional programming with algebraic structures is to avoid unnecessary nesting right at the source.具有代数结构的函数式编程的一部分是避免在源头进行不必要的嵌套。

In your case it does indeed make sense to consolidate everything in a uniform TaskEither - you won't have any advantage with a type IO<TaskEither<...>> vs TaskEither<...> .在您的情况下,将所有内容合并到一个统一的TaskEither中确实TaskEither - 类型IO<TaskEither<...>>TaskEither<...>相比,您不会有任何优势。

2.) How to simply make typescript follow of these type changes going from one to another? 2.) 如何简单地让打字稿跟随这些从一种到另一种类型的变化?

You can use TaskEither.rightIO to transform IO to TaskEither ( CodeSandbox ):您可以使用TaskEither.rightIOIO转换为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[]>>

This one also works, as TS uses structural typing (but I would recommend former):这个也有效,因为 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[]>>>>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM