defectの扱いが思ったよりも複雑なため調査する
ソースコードは前回作ったものを利用する
前回
使用する関数一覧
- catch
- catchAll
- catchAllCause
- catchAllDefect
- catchIf
- catchSome
- catchSomeCause
- catchSomeDefect
- catchTag
- catchTags
前提となる処理
function routeFareCalculation(input: InputDataType) {
const [start, end] = input.route
if (start < 0) {
const die_result = DieMessage("startの値が不正です")
return Effect.die(die_result)
}
if (end < 0) {
const die_result = DieMessage("endの値が不正です")
return Effect.die(die_result)
}
if (start === 777) {
throw 123
}
if (start === end) {
const ng_result = FailMessage("開始位置と終了位置が同じです")
return Effect.fail(ng_result)
} else {
const ok_result = Math.abs(start - end)
return Effect.succeed(ok_result)
}
}
const targetTask = () => routeFareCalculation(input)
const tryEffect = Effect.try(targetTask)
| 変数名 | 型 | 備考 |
|---|---|---|
| targetTask | Effect.Effect<never, FailMessage, never> | Effect.Effect<number, never, never> | 実質的には... : Effect.Effect<number, FailMessage, never> |
| tryEffect | Effect.Effect<Effect.Effect<never, FailMessage, never> | Effect.Effect<number, never, never>, Cause.UnknownException, never> | 実質的には... : Effect.Effect<targetTaskの型, Cause.UnknownException, never> |
| defect_result | Effect.Effect<never, FailMessage, never> | Effect.Effect<number, never, never> | Cause.Cause<Cause.UnknownException> | 実質的には... : targetTaskの型 | Cause.Cause |
tryEffect で Cause.UnknownException が生成されるので、
それをCause.Causeに詰め込むことでエラー処理とする
できるだけ処理が同じようになることを目指す
結果
| 関数名 | 引数で渡される型 | 備考 |
|---|---|---|
| catch | Cause.UnknownException | Effect.cause |
| catchAll | E == Cause.UnknownException | Effect.cause(Effect.fail(e)) |
| catchAllCause | Cause.Cause<E> == Cause.Cause<Cause.UnknownException> | Option.some |
| catchAllDefect | unknown | うまく動作するコードが書けなかった |
| catchIf | E == Cause.UnknownException | Effect.cause |
| catchSome | NoInfer<E> == Cause.UnknownException | Option.some(Effect.cause(e)) |
| catchSomeCause | Cause.Cause<NoInfer<E>> == Cause.Cause<Cause.UnknownException> | Option.some(Effect.cause(Effect.failCause(e))) |
| catchSomeDefect | unknown | うまく動作するコードが書けなかった |
| catchTag | Extract<NoInfer<E>, { _tag: K[number] }> == Cause.UnknownException | Effect.cause |
| catchTags | Extract<E, { _tag: K }> == Cause.Cause<unknown> | Effect.cause |
処理詳細
エラー時の動作
⭕の場合
defect: UnknownException: An unknown error occurred in Effect.try
...
✖の場合
Process exited with code 1
Uncaught FiberFailureImpl UnknownException: An unknown error occurred in Effect.try
...
OK時の動作
start: 123, end: 456, fare: 333
NG-die時の動作
die: startの値が不正です
NG-fail時の動作
fail: 開始位置と終了位置が同じです
⭕と✖
| 関数名 | エラーの動作 | OKの動作 | NG-dieの動作 | NG-failの動作 |
|---|---|---|---|---|
| catch | ⭕ | ⭕ | ⭕ | ⭕ |
| catchAll | ⭕ | ⭕ | ⭕ | ⭕ |
| catchAllCause | ⭕ | ⭕ | ⭕ | ⭕ |
| catchAllDefect | ✖ | ⭕ | ⭕ | ⭕ |
| catchIf | ⭕ | ⭕ | ⭕ | ⭕ |
| catchSome | ⭕ | ⭕ | ⭕ | ⭕ |
| catchSomeCause | ⭕ | ⭕ | ⭕ | ⭕ |
| catchSomeDefect | ✖ | ⭕ | ⭕ | ⭕ |
| catchTag | ⭕ | ⭕ | ⭕ | ⭕ |
| catchTags | ⭕ | ⭕ | ⭕ | ⭕ |
ソース全部
import { Brand, Cause, Effect, Exit, Option, Schema } from "effect"
const InputData = Schema.Struct({
route: Schema.Tuple(Schema.Number, Schema.Number)
})
type InputDataType = typeof InputData.Type
// ---- 料金計算 ----
function routeFareCalculation(input: InputDataType) {
const [start, end] = input.route
if (start < 0) {
const die_result = DieMessage("startの値が不正です")
return Effect.die(die_result)
}
if (end < 0) {
const die_result = DieMessage("endの値が不正です")
return Effect.die(die_result)
}
if (start === 777) {
throw 123
}
if (start === end) {
const ng_result = FailMessage("開始位置と終了位置が同じです")
return Effect.fail(ng_result)
} else {
const ok_result = Math.abs(start - end)
return Effect.succeed(ok_result)
}
}
// ---- OKデータ処理 ----
const RouteAndFareData = Schema.Struct({
route: Schema.Tuple(Schema.Number, Schema.Number),
fare: Schema.Number
})
type RouteAndFareDataType = typeof RouteAndFareData.Type
function displayRouteAndFare(routeAndFare: RouteAndFareDataType) {
const [start, end] = routeAndFare.route
console.log(`start: ${start}, end: ${end}, fare: ${routeAndFare.fare}`)
}
// ---- NGデータ処理 ----
type FailMessage = string & Brand.Brand<"FailMessage">
const FailMessage = Brand.nominal<FailMessage>()
function displayFail(fail: FailMessage) {
console.log(`fail: ${fail}`)
}
type DieMessage = string & Brand.Brand<"DieMessage">
const DieMessage = Brand.nominal<DieMessage>()
function displayDie(die: DieMessage | unknown) {
console.log(`die: ${die}`)
}
function displayDefect(defect: unknown) {
console.log(`defect: ${defect}`)
}
const program = Effect.gen(function*() {
// const input = InputData.make({ route: [123, 456] }) // ok
// const input = InputData.make({ route: [456, 123] }) // ok
const input = InputData.make({ route: [111, 111] }) // ng fail
// const input = InputData.make({ route: [-111, 111] }) // ng die
// const input = InputData.make({ route: [777, 111] }) // ng throw defect
const targetTask = () => routeFareCalculation(input)
const tryEffect = Effect.try(targetTask)
// const defect_result = yield* tryEffect.pipe(
// Effect.catch("_tag", {
// failure: "UnknownException",
// onFailure: Effect.cause
// })
// )
// const defect_result = yield* tryEffect.pipe(
// Effect.catchAll((e) => {
// return Effect.cause(Effect.fail(e))
// })
// )
// const defect_result = yield* tryEffect.pipe(
// Effect.catchAllCause(Option.some)
// )
// const defect_result = yield* tryEffect.pipe(
// Effect.catchAllDefect((e) => {
// return Effect.cause(Effect.fail(e))
// })
// )
// const defect_result = yield* tryEffect.pipe(
// Effect.catchIf(
// (error) => error._tag === "UnknownException",
// Effect.cause
// )
// )
// const defect_result = yield* tryEffect.pipe(
// Effect.catchSome((e) => Option.some(Effect.cause(e)))
// )
// const defect_result = yield* tryEffect.pipe(
// Effect.catchSomeCause((e) => Option.some(Effect.cause(Effect.failCause(e))))
// )
// const defect_result = yield* tryEffect.pipe(
// Effect.catchSomeDefect((e) => Option.some(Effect.cause(Effect.fail(e))))
// )
// const defect_result = yield* tryEffect.pipe(
// Effect.catchTag("UnknownException", Effect.cause)
// )
const defect_result = yield* tryEffect.pipe(
Effect.catchTags({ UnknownException: Effect.cause })
)
if (Cause.isCause(defect_result)) {
displayDefect(defect_result)
} else {
const result = yield* Effect.exit(defect_result)
if (Exit.isSuccess(result)) {
const ok_result = result.value
const routeAndFare = RouteAndFareData.make({
route: input.route,
fare: ok_result
})
displayRouteAndFare(routeAndFare)
} else {
Cause.match(result.cause, {
onFail: (ng_result) => displayFail(ng_result),
onEmpty: undefined,
onDie: (die_result) => {
displayDie(die_result)
},
onInterrupt: (_) => {
throw _
},
onSequential: (_) => {
throw _
},
onParallel: (_) => {
throw _
}
})
}
}
})
Effect.runSync(program)