给定一个结构,其中每个属性的类型为These<E, A>
,其中每个道具的E
和A
不同。
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)
。但这在这里不起作用,因为它也会为两者都返回一个左。
编辑:这些<E、 A>来自fp-ts,并描述了一个可以是E、a或两者的值:https://gcanti.github.io/fp-ts/modules/These.ts.html
第二次编辑:这是我正在努力实现的目标。。基本上是获取结构的所有权限和两个值,同时聚合左侧。然而,它并不完全存在,因为结果还包含类型为never 的左侧属性
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
}
对表述不清的问题表示歉意。这就是我想出的解决方案。可能不是最优雅的,但有效:
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>;
};