1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

effect-tsでキャッチ系をいろいろ試す

Posted at

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)
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?