Skip to content

エラーチャンネル操作

Effect では、エフェクトのエラーチャンネルに対してさまざまな操作を実行できます。これらの操作により、エラーをさまざまな方法で変換、検査、処理することができます。それでは、これらの操作のいくつかを見てみましょう。

マッピング操作

mapError

Effect.mapError関数は、エフェクトが生成したエラーを変換または修正する必要がある場合に使用されます。成功値には影響を与えません。これは、エラーに追加情報を加えたい場合や、その型を変更したい場合に役立ちます。

import { Effect } from "effect";
const simulatedTask = Effect.fail("それは大変だ!").pipe(Effect.as(1));
const mapped = Effect.mapError(simulatedTask, (message) => new Error(message));

プログラムのエラーチャンネルの型がstringからErrorに変更されたことが観察できます。

Effect.mapError関数を使用することにより、エフェクトの全体的な成功や失敗は変わらないことに注意してください。マッピングされたエフェクトが成功している場合、マッピング関数は無視されます。言い換えれば、Effect.mapError操作はエフェクトのエラーチャンネルにのみ変換を適用し、成功チャンネルはそのままにします。

mapBoth

Effect.mapBoth関数を使用すると、エフェクトの両方のチャンネル、すなわちエラーチャンネルと成功チャンネルの変換を適用できます。引数には、エラーチャンネル用と成功チャンネル用の 2 つのマップ関数を受け取ります。

import { Effect } from "effect";
const simulatedTask = Effect.fail("それは大変だ!").pipe(Effect.as(1));
const modified = Effect.mapBoth(simulatedTask, {
onFailure: (message) => new Error(message),
onSuccess: (n) => n > 0,
});

mapBothを使用した後、プログラムの型がEffect<number, string>からEffect<boolean, Error>に変更されたことが観察できます。

mapBoth関数を使用することにより、エフェクトの全体的な成功や失敗は変わらないことに注意してください。エラーチャンネルと成功チャンネルの値が変換されるだけで、エフェクトの元の成功または失敗の状態は保持されます。

成功チャンネルのフィルタリング

Effect ライブラリは、与えられた述語に基づいて成功チャンネル上の値をフィルタリングするためのいくつかの演算子を提供します。これらの演算子は、述語が失敗した場合の処理に対して異なる戦略を提供します。それでは見てみましょう。

関数説明
Effect.filterOrFailこの演算子は、述語に基づいて成功チャンネルの値をフィルタします。述語が任意の値に対して失敗した場合、元のエフェクトはエラーで失敗します。
Effect.filterOrDieおよびEffect.filterOrDieMessageこれらの演算子も、述語に基づいて成功チャンネルの値をフィルタします。述語が任意の値に対して失敗した場合、元のエフェクトは突然終了します。filterOrDieMessageバリアントでは、カスタムエラーメッセージを提供できます。
Effect.filterOrElseこの演算子は、述語に基づいて成功チャンネルの値をフィルタします。述語が任意の値に対して失敗した場合、代わりに別のエフェクトが実行されます。

以下は、これらのフィルタリング演算子がどのように機能するかを示す例です。

import { Effect, Random, Cause } from "effect";
const task1 = Effect.filterOrFail(
Random.nextRange(-1, 1),
(n) => n >= 0,
() => "ランダムな数が負です"
);
const task2 = Effect.filterOrDie(
Random.nextRange(-1, 1),
(n) => n >= 0,
() => new Cause.IllegalArgumentException("ランダムな数が負です")
);
const task3 = Effect.filterOrDieMessage(
Random.nextRange(-1, 1),
(n) => n >= 0,
"ランダムな数が負です"
);
const task4 = Effect.filterOrElse(
Random.nextRange(-1, 1),
(n) => n >= 0,
() => task3
);

使用される特定のフィルタリング演算子によって、エフェクトは失敗するか、突然終了するか、述語が失敗した場合に代わりのエフェクトを実行します。目的のエラー処理戦略とプログラムのロジックに基づいて適切な演算子を選択してください。

以前に議論したフィルタリング機能に加えて、filterOr* API にユーザー定義の型ガードを提供することで、成功チャンネルの型をさらに洗練し、絞り込むことができます。これにより、型安全性が向上するだけでなく、コードの明確さも向上します。この概念を次の例で探ってみましょう。

import { Effect, pipe } from "effect";
// ユーザーインターフェイスを定義
interface User {
readonly name: string;
}
// 非同期認証関数を仮定
declare const auth: () => Promise<User | null>;
const program = pipe(
Effect.promise(() => auth()),
Effect.filterOrFail(
// 型を絞り込むためのガードを定義
(user): user is User => user !== null,
() => new Error("認証されていません")
),
Effect.andThen((user) => user.name) // ここでの'user'は型が`User`です、`User | null`ではありません
);

上記の例では、filterOrFail API 内でガードを使用して、userUser型であることを保証しています。これは、User | nullではなく、洗練された型情報がコードの信頼性を向上させ、理解を容易にします。

簡便さと一貫性のために、Predicate.isNotNullのような事前に作成されたガードを利用することも可能です。

エラーの検査

成功値のTappingと同様に、Effect はエラー値を検査するためのいくつかの演算子を提供します。これらの演算子は、結果に影響を与えずに失敗や基礎的な問題を観察することを可能にします:

関数説明
tapErrorエフェクトの失敗を検査するために効果的な操作を実行しますが、結果には影響を与えません。
tapErrorTag特定のタグを持つ失敗を検査し、集中したエラー処理を可能にします。
tapErrorCauseエフェクトの失敗の根本的な原因を検査します。
tapDefectエフェクト内の回復不可能な失敗や欠陥(すなわち、1 つ以上のDie原因)を具体的に検査します。
tapBothエフェクトの成功と失敗の両方の結果を検査し、結果に基づいて異なるアクションを実行します。

これらのエラー検査ツールを利用することで、エフェクトの結果や型を変えることはありません

tapError

エフェクトの失敗を検査するために効果的な操作を実行しますが、結果には影響を与えません。

import { Effect, Console } from "effect";
// 失敗するように設計されたエフェクトを作成し、ネットワークエラーが発生したことをシミュレート
const task: Effect.Effect<number, string> = Effect.fail("ネットワークエラー");
// タスクが失敗した場合にエラーメッセージをログに記録します。この関数はエラーがある場合にのみ実行され、
// 元のエフェクトの結果を変更することなくエラーを処理または検査する方法を提供します。
const tapping = Effect.tapError(task, (error) =>
Console.log(`予期されたエラー: ${error}`)
);
Effect.runFork(tapping);
/*
出力:
予期されたエラー: ネットワークエラー
*/

tapErrorTag

特定のタグを持つ失敗を具体的に検査し、集中したエラー処理を可能にします。

import { Effect, Console } from "effect";
class NetworkError {
readonly _tag = "ネットワークエラー";
constructor(readonly statusCode: number) {}
}
class ValidationError {
readonly _tag = "検証エラー";
constructor(readonly field: string) {}
}
// 失敗するように設計されたエフェクトを作成し、ネットワークエラーが発生したことをシミュレート
const task: Effect.Effect<number, NetworkError | ValidationError> = Effect.fail(
new NetworkError(504)
);
// "ネットワークエラー"というタグが付けられたエラーにのみエラーハンドリング関数を適用し、
// エラーの対応するステータスコードをログに記録します。
const tapping = Effect.tapErrorTag(task, "ネットワークエラー", (error) =>
Console.log(`予期されたエラー: ${error.statusCode}`)
);
Effect.runFork(tapping);
/*
出力:
予期されたエラー: 504
*/

tapErrorCause

エフェクトの失敗の根本的な原因を検査します。

import { Effect, Console } from "effect";
// 失敗するように設計されたエフェクトを作成し、ネットワークエラーが発生したことをシミュレート
const task1: Effect.Effect<number, string> = Effect.fail("ネットワークエラー");
// このコードは、予期されたエラーや欠陥の原因を記録します
const tapping1 = Effect.tapErrorCause(task1, (cause) =>
Console.log(`エラーの原因: ${cause}`)
);
Effect.runFork(tapping1);
/*
出力:
エラーの原因: Error: ネットワークエラー
*/
// 特定のメッセージで欠陥を引き起こし、システムで重大な失敗をシミュレートします。
const task2: Effect.Effect<number, string> =
Effect.dieMessage("何かがうまくいかなかった");
// このコードは、予期されたエラーや欠陥の原因を記録します
const tapping2 = Effect.tapErrorCause(task2, (cause) =>
Console.log(`エラーの原因: ${cause}`)
);
Effect.runFork(tapping2);
/*
出力:
エラーの原因: RuntimeException: 何かがうまくいかなかった
... スタックトレース ...
*/

tapDefect

回復不可能な失敗や欠陥(すなわち、1 つ以上のDie原因)を具体的に検査します。

import { Effect, Console } from "effect";
// 失敗するように設計されたエフェクトを作成し、ネットワークエラーが発生したことをシミュレート
const task1: Effect.Effect<number, string> = Effect.fail("ネットワークエラー");
// これは欠陥ではないため、何もログに記録されません
const tapping1 = Effect.tapDefect(task1, (cause) =>
Console.log(`欠陥: ${cause}`)
);
Effect.runFork(tapping1);
/*
出力なし
*/
// 特定のメッセージで欠陥を引き起こし、システムで重大な失敗をシミュレートします。
const task2: Effect.Effect<number, string> =
Effect.dieMessage("何かがうまくいかなかった");
// このコードは、欠陥のみをログに記録し、エラーは記録しません
const tapping2 = Effect.tapDefect(task2, (cause) =>
Console.log(`欠陥: ${cause}`)
);
Effect.runFork(tapping2);
/*
出力:
欠陥: RuntimeException: 何かがうまくいかなかった
... スタックトレース ...
*/

tapBoth

エフェクトの成功と失敗の両方の結果を検査し、結果に基づいて異なるアクションを実行します。

import { Effect, Random, Console } from "effect";
// 失敗する可能性のあるエフェクトをシミュレート
const task = Effect.filterOrFail(
Random.nextRange(-1, 1),
(n) => n >= 0,
() => "ランダムな数が負です"
);
// 'task'の成功と失敗の両方の結果をログに記録するエフェクトを定義
const tapping = Effect.tapBoth(task, {
onFailure: (error) => Console.log(`失敗: ${error}`),
onSuccess: (randomNumber) => Console.log(`ランダムな数: ${randomNumber}`),
});
Effect.runFork(tapping);
/*
出力例:
失敗: ランダムな数が負です
*/

成功チャンネルでのエラーの公開

Effect.either関数を使用してEffect<A, E, R>を変換し、失敗(E)と成功(A)の両方のチャンネルをEither<A, E>データ型に持たせることができます。

Effect<A, E, R> -> Effect<Either<A, E>, never, R>

結果として得られるエフェクトは、例外に依存しないエフェクトになります。つまり、左側のケースとして失敗のケースが公開されているため、失敗することはありません。したがって、返される Effect のエラーのパラメータはneverとなり、エフェクトが失敗をモデル化しないことが保証されます。

この関数は、Effect.genを使用して失敗する可能性のあるエフェクトから回復する際に特に便利です。

import { Effect, Either, Console } from "effect";
const simulatedTask = Effect.fail("ああ、そうだ!").pipe(Effect.as(2));
const program = Effect.gen(function* () {
const failureOrSuccess = yield* Effect.either(simulatedTask);
if (Either.isLeft(failureOrSuccess)) {
const error = failureOrSuccess.left;
yield* Console.log(`失敗: ${error}`);
return 0;
} else {
const value = failureOrSuccess.right;
yield* Console.log(`成功: ${value}`);
return value;
}
});

成功チャンネルでの原因の公開

Effect.cause関数を使用して、エフェクトの原因を公開できます。これは、エラーのメッセージや欠陥を含む失敗のより詳細な表現です。

import { Effect, Console } from "effect";
const simulatedTask = Effect.fail("ああ、そうだ!").pipe(Effect.as(2));
const program = Effect.gen(function* () {
const cause = yield* Effect.cause(simulatedTask);
yield* Console.log(cause);
});

エラーチャンネルの成功チャンネルへのマージ

Effect.merge関数を使用して、エラーチャンネルを成功チャンネルにマージすることができ、常にマージされた値で成功するエフェクトを作成します。

import { Effect } from "effect";
const simulatedTask = Effect.fail("ああ、そうだ!").pipe(Effect.as(2));
const merged = Effect.merge(simulatedTask);

エラーと成功チャンネルのフリッピング

Effect.flip関数を使用して、エフェクトのエラーチャンネルと成功チャンネルをフリップし、その役割を効果的に入れ替えることができます。

import { Effect } from "effect";
const simulatedTask = Effect.fail("ああ、そうだ!").pipe(Effect.as(2));
const flipped = Effect.flip(simulatedTask);