微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

嵌套数据获取 fp-ts 末日金字塔

如何解决嵌套数据获取 fp-ts 末日金字塔

首先使用 fp-ts 库进入 typescript 函数式编程的世界。

在这里一个埃及人会引以为豪的“末日金字塔”,我的方法显然做错了。我应该采取什么方法来以不那么命令式的方式解决我的嵌套数据获取问题?

金字塔

export const getProgramWithAllElements = (programId: FirestoreDocumentId): TE.TaskEither<FirestoreError,Program> =>
  pipe(
    getProgram(programId),TE.chain((program) =>
      pipe(
        getCollCollectionsFromPath(program.id),TE.chain((collections) =>
          pipe(
            collections,A.map((collection) =>
              pipe(
                getWeekCollectionsFromPath(program.id)(collection.id),TE.chain((weeks) =>
                  pipe(
                    weeks,A.map((week) =>
                      pipe(
                        getDayCollectionsFromPath(program.id)(collection.id)(week.id),TE.chain((days) =>
                          pipe(
                            days,A.map((day) =>
                              pipe(
                                getExerciseCollectionsFromPath(program.id)(collection.id)(week.id)(day.id),TE.map(
                                  (exercises) =>
                                    ({
                                      ...day,exercises: exercises,} as Day)
                                )
                              )
                            ),A.sequence(TE.taskEither)
                          )
                        ),TE.map(
                          (days) =>
                            ({
                              ...week,days: days,} as Week)
                        )
                      )
                    ),A.sequence(TE.taskEither)
                  )
                ),TE.map(
                  (weeks) =>
                    ({
                      ...collection,weeks: weeks,} as Collection)
                )
              )
            ),A.sequence(TE.taskEither)
          )
        ),TE.map(
          (collections) =>
            ({
              ...program,collections: collections,} as Program)
        )
      )
    )
  );

代码片段中使用的函数

declare const getProgram: (programId: FirestoreDocumentId) => TE.TaskEither<FirestoreError,Program>;

declare const getCollCollectionsFromPath: (
  programId: FirestoreDocumentId
) => TE.TaskEither<FirestoreError,Collection[]>;

declare const getWeekCollectionsFromPath: (
  programId: FirestoreDocumentId
) => (collectionId: FirestoreDocumentId) => TE.TaskEither<FirestoreError,Week[]>;

declare const getDayCollectionsFromPath: (programId: FirestoreDocumentId) => (collectionId: FirestoreDocumentId) => (
  weekId: FirestoreDocumentId
) => TE.TaskEither<FirestoreError,Day[]>;

declare const getExerciseCollectionsFromPath: (programId: FirestoreDocumentId) => (collectionId: FirestoreDocumentId) => (
  weekId: FirestoreDocumentId
) => (dayId: FirestoreDocumentId) => TE.TaskEither<FirestoreError,Exercise[]>;

简化的数据模型

export interface Program {
    id: string;
    // Other Fields
    collections?: Collection[];
}

export interface Collection {
    id: string;
    // Other Fields
    weeks?: Week[];
}

export interface Week {
    id: string;
    // Other Fields
    days?: Day[];
}

export interface Day {
    id: string;
    // Other Fields
    exercises: ProgramExercise[];
}

export interface ProgramExercise {
    id: string;
    // Other Fields
}

解决方法

我不知道 FP-TS,所以我只能提供一般性的答案,但基本的代数规则是相同的。

首先,monads 自然形成嵌套结构。没有办法绕过它,但您可以隐藏它,前提是您拥有正确的工具(Haskell 中的 do 符号)。不幸的是,在 Javascript 中,通常无法从嵌套中抽象出来,但是您可以使用生成器来维护用于确定性计算的平面结构。以下代码取决于我维护的 scriptum lib

const record = (type,o) => (
  o[Symbol.toStringTag] = type.name || type,o);

const Cont = k => record(Cont,{cont: k});

const contOf = x => Cont(k => k(x));

const contChain = mx => fm =>
  Cont(k =>
    mx.cont(x =>
      fm(x).cont(k)));

const _do = ({chain,of}) => init => gtor => {
  const go = ({done,value: x}) =>
    done
      ? of(x)
      : chain(x) (y => go(it.next(y)));

  const it = gtor(init);
  return go(it.next());
};

const log = (...ss) =>
  (console.log(...ss),ss[ss.length - 1]);

const inck = x => Cont(k => k(x + 1));

const sqrk = x => Cont(k =>
  Promise.resolve(null)
    .then(k(x * x)));

const mainM = _do({chain: contChain,of: contOf}) (5) (function* (init) {
  const x = yield inck(init),y = yield sqrk(init);

  return [x,y];
});

mainM.cont(log)

但是,据我从您的代码中可以看出,您实际上并不需要 monad,因为您的下一个计算不依赖于先前的值,例如 chain(tx) (x => x === 0 ? of(x) : of(2/x)。应用函子应该就够了:

const record = (type,{cont: k});

const contMap = f => tx =>
  Cont(k => tx.cont(x => k(f(x))));

const contAp = tf => tx =>
  Cont(k =>
    tf.cont(f =>
      tx.cont(x =>
        k(f(x)))));

const contOf = x => Cont(k => k(x));

const liftA2 = ({map,ap}) => f => tx => ty =>
  ap(map(f) (tx)) (ty);

const contLiftA2 = liftA2({map: contMap,ap: contAp});

const log = (...ss) =>
  (console.log(...ss),ss[ss.length - 1]);

const inck = x => Cont(k => k(x + 1));

const sqrk = x => Cont(k =>
  Promise.resolve(null)
    .then(k(x * x)));

const mainA = contLiftA2(x => y => [x,y]) (inck(5)) (sqrk(5))

mainA.cont(log);

正如我已经提到的,您不能将生成器与非确定性计算(如链表或数组)一起使用。但是,您可以通过应用的方式应用 monad 来减轻痛苦:

const arrChain = mx => fm =>
  mx.flatMap(fm);

const chain2 = chain => mx => my => fm =>
  chain(chain(mx) (x => fm(x)))
    (gm => chain(my) (y => gm(y)));

const log = (...ss) =>
  (console.log(...ss),ss[ss.length - 1]);

const xs = [1,2],ys = [3,4];

main3 = chain2(arrChain) (xs) (ys) (x => [y => [x,y]]);

log(main3);

如您所见,仍然有嵌套结构,但看起来仍然更整洁。

我不完全确定这种技术是否适用于所有 monad,因为它执行效果的频率是正常情况的两倍。到目前为止,我还没有遇到问题,所以我很有信心。

,

这里尝试抽象一些重复的模式:

import * as A from "fp-ts/Array";
import { flow,pipe } from "fp-ts/lib/function";

type FirestoreDocumentId = number;
interface Collection {
  id: number;
}
interface FirestoreError {
  id: number;
}
interface Day {
  id: number;
}
interface Week {
  id: number;
}
interface Program {
  id: number;
}
interface Exercise {
  id: number;
}

declare const getProgram: (
  programId: FirestoreDocumentId
) => TE.TaskEither<FirestoreError,Program>;

declare const getCollCollectionsFromPath: (
  programId: FirestoreDocumentId
) => TE.TaskEither<FirestoreError,Collection[]>;

declare const getWeekCollectionsFromPath: (
  programId: FirestoreDocumentId
) => (
  collectionId: FirestoreDocumentId
) => TE.TaskEither<FirestoreError,Week[]>;

declare const getDayCollectionsFromPath: (
  programId: FirestoreDocumentId
) => (
  collectionId: FirestoreDocumentId
) => (weekId: FirestoreDocumentId) => TE.TaskEither<FirestoreError,Day[]>;

declare const getExerciseCollectionsFromPath: (
  programId: FirestoreDocumentId
) => (
  collectionId: FirestoreDocumentId
) => (
  weekId: FirestoreDocumentId
) => (dayId: FirestoreDocumentId) => TE.TaskEither<FirestoreError,Exercise[]>;

const mapTo = <K extends string>(prop: K) => <T>(obj: T) => <E,A>(
  task: TE.TaskEither<E,A>
) =>
  pipe(
    task,TE.map(
      (data): T & { [P in K]: A } =>
        Object.assign({},obj,{ [prop]: data }) as any
    )
  );

const chainSequence = <E,A,B>(f: (t: A) => TE.TaskEither<E,B>) =>
  TE.chain(flow(A.map(f),A.sequence(TE.taskEither)));

const chainSequenceAndMapTo = <K extends string>(prop: K) => <E,B>(
  f: (id: number) => TE.TaskEither<E,B[]>
) => <A extends { id: number }>(task: TE.TaskEither<E,A[]>) =>
  pipe(
    task,chainSequence((a) => pipe(f(a.id),mapTo(prop)(a)))
  );

export const getProgramWithAllElements = (programId: FirestoreDocumentId) =>
  pipe(
    getProgram(programId),TE.chain((program) =>
      pipe(
        getCollCollectionsFromPath(program.id),chainSequenceAndMapTo("collections")((collectionId) =>
          pipe( 
            getWeekCollectionsFromPath(program.id)(collectionId),chainSequenceAndMapTo("weeks")((weekId) =>
              pipe(
                getDayCollectionsFromPath(program.id)(collectionId)(weekId),chainSequenceAndMapTo("exercises")(
                  getExerciseCollectionsFromPath(program.id)(collectionId)(
                    weekId
                  )
                )
              )
            )
          )
        )
      )
    )
  );

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?