IndexedContを使ってみよう: 前提知識編
この記事は?
IndexedCont
便利なのでみんな使ってほしいとの思いの元、前提となる知識から解説する記事です。
なお、次の次の回ぐらいまでIndexedCont
は 出てきません 。
本編
この記事ではエラーハンドリングのためにIndexedCont
を使えるようになることを目標とします。
Monad Transformer
には触れませんので、別記事を探してください。
Step 1: Optionによるエラーハンドリング
理解しやすいエラーハンドリングの基本として、Option
を使用したエラーハンドリングの例からスタートします。
対象コード
必要十分な最低限の例として、下記のようなオブジェクトを取り扱います。
case class AID(value: Int) case class BID(value: Int) case class CID(value: Int) case class A(id: AID, bID: BID) case class B(id: BID, cID: CID) case class C(id: CID) case clas Found(a: A, b: B, c: C) // 取得用のインターフェース(traitなどに定義): 見つからない場合はNoneを返す def getA(id: AID): Option[A] def getB(id: BID): Option[B] def getC(id: CID): Option[C] def find(aID: AID): Option[Found]
find
を実装するのが目的となります。
実装
取得用インターフェースはわかりやすい例として下記のように実装してみます。
def getA(id: AID): Option[A] = if (id.value % 2 == 0) Some(A(id, BID(id.value * 2))) else None def getB(id: BID): Option[B] = if (id.value > 12) Some(B(id, CID(id.value - 8))) else None def getC(id: CID): Option[C] = if (id.value < 10) Some(C(id)) else None
これに対して、find
の実装は以下のようになります。
def find(aID: AID): Option[Found] = for { a <- getA(aID) b <- getB(a.bID) c <- getC(b.cID) } yield Found(a, b, c)
解説
Option
のfor式はどこかでNone
が返ってきたらその行以降は実行されずにNone
が返ります。
scala> find(AID(5)) // Aが見つからない res0: Opiton[Found] = None scala> find(AID(6)) // Bが見つからない res1: Opiton[Found] = None scala> find(AID(18)) // Cが見つからない res2: Opiton[Found] = None scala> find(AID(8)) // すべて見つかる res3: Opiton[Found] = Some(Found(A(AID(8), BID(16)), B(BID(16), CID(8)), C(CID(8))))
Step 2: Eitherを使用してエラー内容を特定する
Option
ではどこか途中で失敗すると、どこで失敗してもNone
となります。
どこで失敗したかを知りたい場合の一番シンプルな方法がEither
です。
対象コード
Option
の例そのままで、find
だけ変更します。
def find(aID: AID): Either[String, Found]
実装
こちらもfind
以外はそのままです。
def find(aID: AID): Either[String, Found] = for { a <- getA(aID).toRight(s"AID: ${aID}のAが見つかりませんでした。") b <- getB(a.bID).toRight(s"BID: ${a.bID}のBが見つかりませんでした。") c <- getC(b.cID).toRight(s"CID: ${b.cID}のCが見つかりませんでした。") } yield Found(a, b, c)
解説
Either
もOption
と同様に1度でもLeft
になるとそれ以降の行は実行されません。
scala> find(AID(5)) // Aが見つからない res0: Either[String, Found] = Left("AID: 5のAが見つかりませんでした。") scala> find(AID(6)) // Bが見つからない res1: Either[String, Found] = Left("BID: 12のBが見つかりませんでした。") scalva> find(AID(19)) // Cが見つからない res2: Either[String, Found] = Left("CID: 11のCが見つかりませんでした。") scala> find(AID(8)) // すべて見つかる res3: Either[String, Found] = Right(Found(A(AID(8), BID(16)), B(BID(16), CID(8)), C(CID(8))))
ただし、ひとつのfor式の中で使われるEither
はすべて左側の型が同じである必要があります。
応用
左側がString
では扱いにくいので、自身で定義した型をここに入れたりできます。
sealed trait FindingError case class ANotFound(id: AID) extends FindingError case class BNotFound(id: BID) extends FindingError case class CNotFound(id: CID) extends FindingError def find(aID: AID): Either[FindingError, Found] = for { a <- getA(aID).toRight[FindingError](ANotFound(aID)) b <- getB(a.bID).toRight(BNotFound(a.bID)) c <- getC(b.cID).toRight(CNotFound(b.cID)) } yield Found(a, b, c)
次回にむけて
次回はIndexedCont
のシンプルな形Cont
を扱います。
たぶん今回の記事にも時間があれば追記します。 (コードを実際に動かす時間がなかったので動かしてみて修正したりします。)