【问题标题】:Struct of these这些结构
【发布时间】:2022-01-04 17:16:45
【问题描述】:

给定一个结构,其中每个属性的类型为 These<E, A>,其中 EA 对于每个属性都不同。

declare const someStruct: {
    a1: TH.These<E1, A1>;
    a2: TH.These<E2, A2>;
    a3: TH.These<E3, A3>;
}

我正在这样对待These

  • 左:严重错误,计算失败
  • 正确:计算成功
  • 两者:轻微错误/警告,继续计算

现在我正在寻找一种方法来组合上述结构的结果

declare function merge(struct: Record<string, TH.These<unknown, unknown>>): E.Either<CriticalErrorLeftOnly, {
    warnings: unknown[]; // these would be the E of Both
    value: Record<string, unknown>;
}>

如果我可以做到sequenceS(E.Apply)(someStruct)。但这在这里行不通,因为它也会为两者返回一个左键。

编辑: 这些 来自 fp-ts 并描述了一个可以是 E、A 或两者的值:https://gcanti.github.io/fp-ts/modules/These.ts.html

第二次编辑: 这是我想要实现的 POC .. 基本上获得结构的所有权利和两个值,同时聚合左侧。然而它并不完全在那里,因为结果还包含类型为 never 的 lefty 属性

import * as ROA from 'fp-ts/ReadonlyArray';
import * as TH from 'fp-ts/These';
import * as E from 'fp-ts/Either';

type PropertyKey = string | number | symbol;
type AnyRightThese = TH.These<any, any>;
type PropertyError<Properties> = { key: keyof Properties; error: Core.Error.Type };
type UnwrapRight<T> = T extends E.Right<infer A> ? A : never;

export const collect = <Properties extends Record<PropertyKey, AnyRightThese>>(
  properties: Properties,
): TH.These<
  PropertyError<Properties>[],
  {
    [K in keyof Properties]: UnwrapRight<Properties[K]>;
  }
> => {
  const errorsAndWarnings: PropertyError<Properties>[] = [];
  const rights: any = {};

  let isBoth = true;

  for (const k in properties) {
    const de = properties[k];

    if (TH.isLeft(de)) {
      isBoth = false;
      errorsAndWarnings.push({ key: k, error: de.left });
    } else if (TH.isRight(de)) {
      rights[k] = de.right;
    } else {
      errorsAndWarnings.push({ key: k, error: de.left });
      rights[k] = de.right;
    }
  }

  return ROA.isNonEmpty(errorsAndWarnings)
    ? isBoth
      ? TH.both(errorsAndWarnings, rights)
      : TH.left(errorsAndWarnings)
    : TH.right(rights);
};

// example
const struct = {
  a: TH.right(1),
  b: TH.left('foo'),
  c: TH.both(10, 'foobar'),
};

const a = collect(struct);

if (TH.isRight(a)) {
  a.right.b; // b should not be part of a as it is of type never
}

【问题讨论】:

  • 什么是These?请分享该类型的定义。
  • 更新了问题。这些值可以是 E、A 或两者,来自 fp-ts:gcanti.github.io/fp-ts/modules/These.ts.html
  • 问题是如何输入(使用泛型而不是unknown)还是如何实现?对于后者,我怀疑你会做自己的matchW 事情
  • These&lt;A, B&gt; 等价于Either&lt;A, Either&lt;B, Tuple&lt;A, B&gt;。考虑到这一点,您可以构造有效的函子、应用程序和可遍历的实例。这是implementation in purescript

标签: typescript functional-programming fp-ts


【解决方案1】:

对于表述不清的问题,我们深表歉意。这是我想出的解决方案。可能不是最优雅的,但工作:

import { match } from 'ts-pattern';
import { pipe } from 'fp-ts/lib/function';
import * as E from 'fp-ts/Either';
import * as ROA from 'fp-ts/ReadonlyArray';
import * as ROR from 'fp-ts/ReadonlyRecord';
import * as S from 'fp-ts/string';
import * as TH from 'fp-ts/These';

type PropertyKey = string | number | symbol;
type AnyLeft = E.Left<any>; // eslint-disable-line
type AnyRight = E.Right<any>; // eslint-disable-line
type AnyBoth = TH.Both<any, any>; // eslint-disable-line
type AnyThese = TH.These<any, any>; // eslint-disable-line
type UnwrapLeft<T> = T extends E.Left<infer E> ? E : never;
type UnwrapRight<T> = T extends E.Right<infer A> ? A : never;

type Partitioned = {
  boths: Record<string, AnyBoth>;
  lefts: Record<string, AnyLeft>;
  rights: Record<string, AnyRight>;
};

const partition = (fa: ROR.ReadonlyRecord<string, AnyThese>): Partitioned =>
  pipe(
    fa,
    ROR.reduceWithIndex(S.Ord)({ boths: {}, lefts: {}, rights: {} } as Partitioned, (k, acc, v) =>
      match(v)
        .with({ _tag: 'Both' }, (b) => ({ ...acc, boths: { ...acc.boths, [k]: b } }))
        .with({ _tag: 'Left' }, (l) => ({ ...acc, lefts: { ...acc.lefts, [k]: l } }))
        .with({ _tag: 'Right' }, (r) => ({ ...acc, rights: { ...acc.rights, [k]: r } }))
        .exhaustive(),
    ),
  );

const collectLefts = <E>(fa: ROR.ReadonlyRecord<string, E.Left<E> | TH.Both<E, unknown>>) =>
  pipe(
    fa,
    ROR.toReadonlyArray,
    ROA.map((s) => s[1]),
    ROA.filterMap(TH.getLeft),
  );

type CollectedTheseS<Properties extends Record<PropertyKey, AnyThese>> = TH.These<
  UnwrapLeft<Properties[keyof Properties]>[],
  {
    [K in keyof Properties as Properties[K] extends TH.These<any, never> ? never : K]: UnwrapRight<
      Properties[K]
    >;
  }
>;

export const sequenceSCollectLefts = <Properties extends Record<PropertyKey, AnyThese>>(
  properties: Properties,
): CollectedTheseS<Properties> => {
  const { boths, lefts, rights } = partition(properties);
  const errors = collectLefts(lefts);
  const warnings = collectLefts(boths);

  return (
    errors.length > 0
      ? TH.left(errors)
      : warnings.length > 0
      ? TH.both(warnings, ROR.filterMap(TH.getRight)({ ...boths, ...rights }))
      : TH.right(ROR.filterMap(TH.getRight)(rights))
  ) as CollectedTheseS<Properties>;
};

【讨论】:

    猜你喜欢
    • 2013-12-20
    • 2022-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多