2012年9月25日火曜日

Scala Tips / Scala 2.10味見(13) - Try(5) 基本フォーム2

前回はTryの基本フォームとして以下のものを考えました。

def f(a: Int, b: Int) {
  Try { // Try処理の本体
    a / b
  } map { // 例外が発生しない処理
    x => x + 1
  } flatMap { // 例外が発生する処理
    x => Try {
      x / (b - 1)
    }
  } match { // 終了処理
    case Success(v) => println(v)
    case Failure(e) => println(e)
  }
}

実行結果は以下のようになります。

scala> f(100, 0)
java.lang.ArithmeticException: / by zero

scala> f(100, 2)
51

scala> f(100, 1)
java.lang.ArithmeticException: / by zero

TryのmapメソッドやflatMapメソッドといったコンビネータをこのようにつないで一種のパイプラインをつくるのが、モナドらしい使い方です。

ただし、このプログラムのように関数リテラルをそのまま記述するようにすると、プログラムの見通しが悪くなるという問題があります。

また、関数型言語における関数は再利用の重要な候補です。可能であれば関数を部品化する方向に持って行きたいというニーズもあります。

内部関数

こういったニーズに適合するのが内部関数です。

前述の関数fを内部関数を用いて書き直したものが以下になります。

def f(a: Int, b: Int) {
  def divAB = a / b
  def plus1(x: Int) = x + 1
  def minus100DivB(x: Int) = Try(x / (b - 1))

  Try(divAB).map(plus1).flatMap(minus100DivB) match {
    case Success(v) => println(v)
    case Failure(e) => println(e)
  }
}

3つの内部関数divAB, plus1, minus100DivBを用意し、起点のTry、mapコンビネータ、flatMapコンビネータに合成しています。終了処理は普通にmatch式を用います。

効果の一つとして、Tryを起点にするパイプラインの全体像が分かりやすくなっています。

また、パイプラインを構成する書く関数に名前がついているので、プログラムとしての可読性も上がっています。処理の内容をコメントで残すより、可読性の高い関数名をつけた関数に分解して処理を合成したほうが合理的ですね。

さらに、内部関数の中で汎用性がありそうなものは、外部に出して再利用可能な部品化していくという道筋をつけることもできます。

ノート

今回の内部関数によるイディオムは、Tryに限らずモナドを使ったモナディック・プログラミングでは頻出です。そういう意味では、Tryも普通のモナディック・プログラミングの枠組みで使うのがよい感じということです。例外による異常処理をモナディック・プログラミングに載せる機構がTry、ということもいえますね。

諸元

  • Scala 2.10.0-M7

0 件のコメント:

コメントを投稿