fp-tsライブラリを使った部分欠損した状態のデータ構造について
こんにちわ。nap5です。
fp-tsライブラリを使った部分欠損した状態のデータ構造について紹介したいと思います。
たとえば、5個アイテムがあってそのうち偶数番目のアイテムだけエラーが起きた場合を考慮します。
import { z } from "zod";
import { TaskEither } from "fp-ts/lib/TaskEither";
import { tryCatch } from "fp-ts/lib/TaskEither";
import { sequenceT } from "fp-ts/lib/Apply";
import { ApplyPar } from "fp-ts/lib/Task";
const CustomErrorDataSchema = z.custom<CustomError>();
type CustomErrorData = z.infer<typeof CustomErrorDataSchema>;
type UserId = number;
class CustomError extends Error {
constructor(message: string, option?: { cause: unknown }) {
super(message, option);
}
}
const doN = (n: number): TaskEither<CustomErrorData, UserId> => {
return tryCatch(
async () => {
if (n % 2 === 0) {
return Promise.reject(
new CustomError(`Something went wrong... [${n}]`)
);
}
return n;
},
(e) => e as CustomErrorData
);
};
(async () => {
const results = await sequenceT(ApplyPar)(
doN(1),
doN(2),
doN(3),
doN(4),
doN(5)
)();
console.log(results)
})();
実行結果になります。2番目のアイテムと4番目のアイテムがこけていることがわかります。エラーが途中で起きていても、早期終了するのではなく、順次処理をしているのがポイントです。
[
{ _tag: 'Right', right: 1 },
{
_tag: 'Left',
left: CustomError: Something went wrong... [2]
at /home/someone/wrksp/src/1.ts:22:11
at /home/someone/wrksp/node_modules/fp-ts/lib/TaskEither.js:229:42
at step (/home/someone/wrksp/node_modules/fp-ts/lib/TaskEither.js:56:23)
at Object.next (/home/someone/wrksp/node_modules/fp-ts/lib/TaskEither.js:37:53)
at /home/someone/wrksp/node_modules/fp-ts/lib/TaskEither.js:31:71
at new Promise (<anonymous>)
at __awaiter (/home/someone/wrksp/node_modules/fp-ts/lib/TaskEither.js:27:12)
at /home/someone/wrksp/node_modules/fp-ts/lib/TaskEither.js:223:33
at async Promise.all (index 1)
},
{ _tag: 'Right', right: 3 },
{
_tag: 'Left',
left: CustomError: Something went wrong... [4]
at /home/someone/wrksp/src/1.ts:22:11
at /home/someone/wrksp/node_modules/fp-ts/lib/TaskEither.js:229:42
at step (/home/someone/wrksp/node_modules/fp-ts/lib/TaskEither.js:56:23)
at Object.next (/home/someone/wrksp/node_modules/fp-ts/lib/TaskEither.js:37:53)
at /home/someone/wrksp/node_modules/fp-ts/lib/TaskEither.js:31:71
at new Promise (<anonymous>)
at __awaiter (/home/someone/wrksp/node_modules/fp-ts/lib/TaskEither.js:27:12)
at /home/someone/wrksp/node_modules/fp-ts/lib/TaskEither.js:223:33
at async Promise.all (index 1)
},
{ _tag: 'Right', right: 5 }
]
これをもとにサマリと明細のデータ構造にそろえてやると、バッチ処理のようなインターフェースを持つプログラムが作りやすくなります。これを達成するためにcollect関数を正常系と異常系の2つ用意します。
import { getSemigroup } from "fp-ts/lib/Array";
import { Either, isLeft as isError } from "fp-ts/lib/Either";
const collectError = <E>(results: Either<E, unknown>[]) => {
const S = getSemigroup<E>();
const errors = results
.map((result) => {
if (isError(result)) {
return result.left;
}
})
.filter((item): item is E => !!item);
return S.concat(errors, []);
};
const collectData = <A>(results: Either<unknown, A>[]) => {
const S = getSemigroup<A>();
const data = results
.map((result) => {
if (!isError(result)) {
return result.right;
}
})
.filter((item): item is A => !!item);
return S.concat(data, []);
};
これらを使うと以下のような出力結果を作りやすくなります。
pretty-formatライブラリを使ってNeatな感じに出力してます。
$ yarn do-2
Object {
"detail": Object {
"data": Array [
1,
3,
5,
],
"errors": Array [
[Error: Something went wrong... [2]],
[Error: Something went wrong... [4]],
],
"results": Array [
Object {
"_tag": "Right",
"right": 1,
},
Object {
"_tag": "Left",
"left": [Error: Something went wrong... [2]],
},
Object {
"_tag": "Right",
"right": 3,
},
Object {
"_tag": "Left",
"left": [Error: Something went wrong... [4]],
},
Object {
"_tag": "Right",
"right": 5,
},
],
},
"summary": Object {
"dataCount": 3,
"errorCount": 2,
"resultCount": 5,
},
}
これを使うとタグがLeftの場合、つまり異常が起きたアイテムのみリカバリ処理のUIを実現するためのアプローチが取りやすくなります。
デモコードです。
簡単ですが、以上です。
この記事が気に入ったらサポートをしてみませんか?