Как управлять монадами в FP и особенно в fp-ts - PullRequest
0 голосов
/ 18 марта 2020

Я очень новичок в функциональном программировании и, особенно, в библиотеке fp-ts.

Мой вопрос состоит из двух частей:

  1. Я вижу схему превращения монад из одного введите другое значение, например, от 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 Ответ

3 голосов
/ 18 марта 2020

1.) Как превратить монады из одного типа в другой, как нам это сделать, мы должны всегда оставаться на одном или мы должны меняться по мере продолжения цепочки?

Вы хотите, чтобы некоторые вид (естественного) преобразования для переключения с IO на Task / TaskEither. Обратный путь для меня не имеет смысла, поскольку асин c эффект не может быть преобразован в син c.

chain сохранит структуру . Так что getPage в IO.chain(getPage) нужна подпись number -> IO<whatever>. Вместо этого вы можете использовать map, чтобы добавить дополнительный слой вложенности, например:

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

В общем, нет правильного или неправильного пути, это просто зависит от цели. Обратите внимание: чем больше вложенных типов данных, тем сложнее будет обрабатывать внутренние значения. Часть функционального программирования с алгебраическими c структурами состоит в том, чтобы избежать ненужного вложения прямо в источник.

В вашем случае действительно имеет смысл объединить все в единую TaskEither - у вас не будет никаких преимущество с типом IO<TaskEither<...>> против TaskEither<...>.

2.) Как просто заставить машинопись следовать за этими изменениями типов при переходе от одного к другому?

Вы можно использовать TaskEither.rightIO для преобразования IO в 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[]>>>>
...