2012年8月31日金曜日

Scala Tips / Scala 2.10味見(3) - Future

Scala 2.10の目玉機能の一つは並行プログラミング機能の大幅な刷新です。「SIP-14 - Futures and Promises」にもあるようにScala Aactors、Akka、Finagle、Scalazと乱立する並行プログラミング・フレームワークの共通基盤を提供するのが目的のようです。

並行プログラミングの重要機能の一つにfutureまたはpromiseがあります。Wikipediaによるとfutureとpromiseは基本的に同じ意味を持つと考えてよいようです。

Scala 2.10ではFutureとPromiseの2つのオブジェクトを導入していますが、futureとpromiseの用語法は知った上で、あえて別機能を持った2つのオブジェクトとして提供している、ということだと思います。

今回はその一つFutureがテーマです。Futureの機能も色々あるのでまず簡単な使い方を調べてみます。

準備

準備として引数の数値×100msウェイト後、引数の数値をそのまま帰す関数gを定義します。

val g = (x: Int) => {  
  Thread.sleep(x * 100)
  x  
}

値を取り出す

Futureオブジェクトの最も典型的な使い方である処理を非同期実行した後、同期待ち合わせを行い、実行結果を受け取る処理です。

def f1: Int = {
  import scala.concurrent._
  import scala.concurrent.util.Duration
  import ExecutionContext.Implicits.global

  val f = future { g(1) }
  Await.result(f, Duration.Inf)
}

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

scala> f1
res154: Int = 1

futureと聞いてイメージするのは、java.util.concurrent.Futureもそうなっているように、futureの実行結果をgetメソッドで取得すると自動的に非同期実行の待ち合わせを行なってくれ、非同期実行を意識することなく実行結果を得ることができるオブジェクトです。

しかし2.10のFutureではgetメソッドは提供されておらず、現在の状況をノンブロッキングで取得するvalueメソッドが提供されています。そして、Futureの結果をブロックして取得するには外付けのAwaitオブジェクトを用いるようになっています。

SIP-14にも「blocking on a future is strongly discouraged」とありますので、この使い方はあまり推奨されていないようです。

Callback

その代わりに推奨されているのがCallbackです。mapやflatMapといったコンビネータを使って関数を合成していく使い方で、いわゆるCPS(Continuation passing style)と通じるスタイルです。

以下では関数gをFuture内で実行した後、mapメソッドで再度実行させ、その結果をonCompleteメソッドで受け取っています。

def f2 { 
  import scala.concurrent._
  import scala.concurrent.util.Duration
  import scala.util.{Success, Failure}
  import ExecutionContext.Implicits.global

  val f = future { g(1) } map { x => 
    g(x + 1)
  } onComplete {
    case Success(x) => println("success = " + x)
    case Failure(e) => println("failure = " + e)
  }
}

f2を実行結果すると…

scala> f2

しばらくするとコンソールに以下の文字列が表示されます。

success = 2
Try

FutureのonCompleteメソッドは以前はEitherを渡してきましたが、M7ではTryを渡すようになっています。

Togetterのscala.util.TryによるとTryはあまり評判がよくないようなので、もしかしたら再度仕様変更があるかもしれません。

ノート

2.10のFutureオブジェクトはfutureの実行待ち合わせを行うgetメソッドが基本機能として提供されていないことから分かるように、CPS的な並行プログラミングを指向した設計思想のようです。

これは、Actorと組合せて使うことを念頭に置いているのも影響しているかもしれません。

イベント駆動のアプリケーションを作る場合は、CPSスタイルでもよいのですが、大規模演算を並列実行したいような場合は"普通の"futureの使い勝手が欲しいような気もします。たとえばScalazのPromiseのような使い方ですね。

2.10の並行プログラミング周りはまだまだ仕様が安定していない感じなので、この点も含めて今後どうなるかウォッチしていきたいと思います。

諸元

  • Scala 2.10.0-M7

2012年8月30日木曜日

Scala Tips / Scala 2.10味見(2) - Type Dynamic

Type DynamicはRubyのmethod_missingのようなオブジェクトに動的にメソッドやフィールドを追加する機能です。トレイトscala.Dynamicを拡張したクラスに、動的にメソッドやフィールドを定義する機能を追加します。

Scala 2.9でexperimentalとしてサポートされていましたが、Scala 2.10から正式にサポートされることになりました。ただし、無軌道には使って欲しくはないということだと思いますが、2.10からサポートされるModularizing Language Featuresで指定した時のみ使用できるようになっています。

Type DynamicはScalaの静的型付けを破壊するので積極的には使うべきではありませんが、ActiveRecordなどの実装では効果抜群なのでDSLの実装技術としては押さえておきたいところです。

Type Dynamic

ActiveRecordをイメージして、トレイトDynamicを拡張したクラスRecordを定義します。実装は、入力されたパラメタをコンソールに表示するのみになっています。

import language.dynamics

class Record extends Dynamic {
  def applyDynamic(name: String)(args: Any*): Any = {
    println(s"applyDynamic(${name})(${args})")
  }

  def applyDynamicNamed(name: String)(args: (String, Any)*): Any = {
    println(s"applyDynamicNamed(${name})(${args})")
  }

  def selectDynamic(name: String): Any = {
    println(s"selectDynamic(${name})")
    name
  }

  def updateDynamic(name: String)(arg: Any) {
    println(s"updateDynamic(${name})(${arg})")
  }
}

最初に「import language.dynamics」でDynamicの使用を宣言します。

クラスRecordでは、以下の4つのメソッドを定義しています。トレイトDynamicを拡張したクラスに対して定義されていないメソッドやフィールドへのアクセスが発生すると、これらのメソッドが呼びだされます。

applyDynamic
メソッド呼び出しをインターセプト
applyDynamicNamed
名前付きパラメタを持ったメソッド呼び出しをインターセプト
selectDynamic
フィールドへの参照をインターセプト
updateDynamic
フィールドの更新をインターセプト

それぞれの実装は、Interpolationで受け取った引数を整形して、printlnでコンソールに表示するものになっています。この部分のコードを実装すれば、ActiveRecordも容易に実現できそうです。

実行

それでは、実際に動作させてみましょう。

まずRecordのインスタンスを生成します。

scala> val r = new Record
r: Record = Record@320ba1cc

メソッド呼び出しは以下になります。applyDynamicメソッドが呼ばれているのがわかります。

scala> r.foo("bar", "boo")
applyDynamic(foo)(WrappedArray(bar, boo))

名前付きパラメタのメソッド呼び出しは以下になります。applyDynamicNamedメソッドが呼ばれているのがわかります。

scala> r.foo(x = "bar", "boo")
applyDynamicNamed(foo)(WrappedArray((x,bar), (,boo)))

フィールドの参照は以下になります。selectDynamicメソッドが呼ばれているのがわかります。

scala> r.foo
selectDynamic(foo)
res153: String = foo

フィールドの更新は以下になります。updateDynamicメソッドが呼ばれているのがわかります。

scala> r.foo = 10
updateDynamic(foo)(10)
selectDynamic(foo)
r.foo: String = foo

updateDynamicメソッドの後に、selectDynamicメソッドが呼ばれていますが、これはREPLがrオブジェクトのfooフィールドの内容を表示するために呼び出しているものと考えられます。

注意

updateDynamicメソッドはREPLでは動作していますが、プログラム内で使うと「r.foo = 10」のところが以下のコンパイルエラーになってしまいました。

[error] .../DynamicInvocation.scala:14: value update is not a member of String
[error]     r.foo() = 10
[error]       ^

以下のようなバグがまだ残っているようです。

ノート

applyDynamic, applyDynamicNamed, selectDynamicの各メソッドはここでは仮に返却値の型をAnyにしていますが、これをどうするのかというのが実装上の課題です。

仕様上は「def applyDynamic[T](name: String)(args: Any*): T」というように型パラメータを使うのも可能なので、外部仕様上これをどう見せていくのか、内部実装はどうやっていくのかという所は考えどころになります。

諸元

  • Scala 2.10.0-M7

開発環境の都合で、REPLはScala 2.10.0-M6を使っています。

2012年8月29日水曜日

Scala Tips / Scala 2.10味見 - Interpolation

Scala 2.10.0もMilestone 7がリリースされ、そろそろ仕上がってきた感じです。そこで、ぼちぼち味見を始めることにしました。

ボク的にはScalaの内部DSL機能を強化する言語機能と並列プログラミングに興味があります。そういう意味で2.10では以下の機能をチェックしてみたいと考えています。

また2.8でexperimentalで入ったはずの限定継続の現状も興味があるところです。

Interpolation

Interpolationはざっくりいうと文字列の中にScalaプログラムを埋め込んで評価結果を文字列として取り出すことができる機能のようです。

典型的な使い方が以下になります。文字列の前に「s」をつけると「$」のプレフィックスで指定したScalaの式が評価されて文字列に埋め込まれます。Scalaの式は1文字の場合は「$c」の形式も可能です。2文字以上になると「${expr}」の形式になります。

def interpolations {
  val h = "Hello"
  val w = "World"
  val s = s"$h, $w"
  println(s)
}

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

scala> interpolations
Hello, World
format

Interpolationでは文字列の前に指定する識別子で振舞いを変えることができます。前述の「s」は単純に文字列を埋め込むものでしたが、それ以外に識別子「f」が用意されています。

識別子「f」を指定するとjava.util.Formatterの文法に従って埋め込む文字列を整形します。

使用例は以下になります。

def interpolationf {
  val label = "total"
  val number = 1234567.89
  val s = f"${label}: ${number}%,3.1f"
  println(s)
}

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

scala> interpolationf
total: 1,234,567.9

DSL

Interpolationも、識別子「s」と「f」が提供されるだけであれば、便利機能の提供というレベルのインパクトですが、なんと識別子をアプリケーションで自由に追加することができます。この機能を用いればDSLにも利用できそうです。

まず、DSLの受け側としてケースクラスDslを用意します。

Interpolationに新しい識別子dslを追加するためにはStringContextに拡張メソッドdslを追加する必要があります。この目的でこれもScala 2.10で追加されたimplicit classesとしてDslStringContextを定義しました。

case class Dsl(strings: Seq[String])

object Dsls {
  implicit class DslStringContext(self: StringContext) {
    def dsl(args: Any*): Dsl = {
      val a = self.parts.map(Some(_)).zipAll(args.map(_.toString).map(Some(_)), None, None)
      val b = a.flatMap(x => Seq(x._1, x._2)).flatten
      Dsl(b)
    }
  }
}

これを使って新しい識別子dslを用いると以下になります。

def interpolationdsl {
  import Dsls._

  val table = "MyTable"
  val cond = "x > 100"
  val d = dsl"select * from ${table} where ${cond}"
  println(d)
}

実行結果は以下のとおりです。

scala> interpolationdsl
Dsl(ArrayBuffer(select * from , MyTable,  where , x > 100, ))

Stringをinterpolation(改竄)した結果は必ずしもStringである必要はないようです。この場合はケースクラスDslを生成して返すことができました。

この例ではDSLを記述する文字列としてSQLを用いてみました。これは任意のDSLを文字列としてScalaプログラムに埋め込むことを狙っていますが、良い感触を得ることができました。

この結果、Scalaをホスト言語とする内部DSLと、Interpolationで記述した外部DSLをシームレスに接続する道筋が見えてきました。

Interpolationを普通に使うとプログラムの実行時にDSLの文法チェックを行うことになりますが、マクロと組み合わせることでコンパイル時の文法チェックも可能になると思われます。

Interpolation(+マクロ)はDSLの実現方式に大きなインパクトがありそうですね。

諸元

  • Scala 2.10.0-M7

2012年8月28日火曜日

Scala Tips / Option(16) - First, Last, Max, Min その2

OptionのTagの使用方法に抜けがあったので追加情報です。

ねこはるさんから情報をいただきました。ありがとうございます。

Tags.First

Tags.Firstのタグ付けの方法です。

前回の記事では、Tagメソッド(scalaz.Tagのapplyメソッド)を用いる以下の2つの方法を紹介しました。いずれも型を正確に指定しないといけないので使い方はやや煩雑です。

val a: Option[Int] @@ Tags.First = Tag(1.some)
val b: Option[Int] @@ Tags.First = Tag(2.some)
val c: Option[Int] @@ Tags.First = Tag(3.some)
val n: Option[Int] @@ Tags.First = Tag(none)
val a = Tag[Option[Int], Tags.First](1.some)
val b = Tag[Option[Int], Tags.First](2.some)
val c = Tag[Option[Int], Tags.First](3.some)
val n = Tag[Option[Int], Tags.First](none)

実はTags.Firstの指定は以下の方法で可能です。

val a = Tags.First(1.some)
val b = Tags.First(2.some)
val c = Tags.First(3.some)
val n = Tags.First(none[Int])

この方法だとOption[Int]に関する型情報を陽に指定しなくても自動的に判別してくれます。

Tags.Firstメソッドは実装にTagメソッドを使っています。Tagメソッドは、Tags.Firstメソッドのようなメソッドを実装するためのユーティリティ・メソッドとして考えるとよさそうです。

Tags.Last

Tags.Lastのタグ付けは以下になります。

val a = Tags.Last(1.some)
val b = Tags.Last(2.some)
val c = Tags.Last(3.some)
val n = Tags.Last(none[Int])

Tags.Max

Tags.Maxのタグ付けは以下になります。

val a = Tags.Max(1.some)
val b = Tags.Max(2.some)
val c = Tags.Max(3.some)
val n = Tags.Max(none[Int])

Tags.Min

Tags.Minのタグ付けは以下になります。

val a = Tags.Min(1.some)
val b = Tags.Min(2.some)
val c = Tags.Min(3.some)
val n = Tags.Min(none[Int])

ノート

いわずもがなですが、各Tagはstatic importすると短い名前で使用することができます。

import Tags._

val a = First(1.some)
val b = First(2.some)
val c = First(3.some)
val n = First(none[Int])

static importをやり過ぎるとプログラムの可読性が落ちてしまうので、ボクのマイルールでは一度や二度出てくる程度の場合はTags.Firstのようにqualified nameで書くようにしています。Emacs +ensimeだと、ensimeによるオブジェクト名やメソッド名の補完に加えてEmacsの基本機能であるdynamic abbreviationがあるのでqualified nameを書くのも(比較的)苦になりません。

諸元

  • Scala 2.10.0-M6
  • Scalaz 7.0.0-M2

2012年8月27日月曜日

Scala Tips / Option(15) - First, Last, Max, Min

前回説明した通り、Scalaz 7のOptionはTagを用いてMonoid演算の振舞いを制御できます。

Optionでは以下の4つのTagが定義されています。

  • Tags.First
  • Tags.Last
  • Tags.Max
  • Tags.Min

Tagを指定していない場合を加えると、Monoid演算で5種類の振舞いを選択することができます。

デフォルト

まずデフォルトの動きを確認しましょう。

以下のOptionを定義します。

val a = 1.some
val b = 2.some
val c = 3.some
val n = none[Int]

Monoid演算の結果は以下になります。|+|は2つのMonoid間のMonoid演算、ListのsumrメソッドはMonoidの畳込みです。

scala> a |+| b
res65: Option[Int] = Some(3)

scala> n |+| b
res66: Option[Int] = Some(1)

scala> a |+| n
res67: Option[Int] = Some(2)

scala> n |+| n
res68: Option[Int] = None

scala> List(a, b, c).sumr
res69: Option[Int] = Some(6)

scala> List(n, b, c).sumr
res70: Option[Int] = Some(5)

scala> List(a, n, c).sumr
res71: Option[Int] = Some(4)

scala> List(a, b, n).sumr
res72: Option[Int] = Some(3)

Tags.First

次はTags.Firstのタグ付けをした場合です。

val a: Option[Int] @@ Tags.First = Tag(1.some)
val b: Option[Int] @@ Tags.First = Tag(2.some)
val c: Option[Int] @@ Tags.First = Tag(3.some)
val n: Option[Int] @@ Tags.First = Tag(none)

Monoid演算の結果は以下になります。Monoid演算の結果、最初に有効なOptionが選択されていることが分かります。

scala> a |+| b
res37: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

scala> a |+| n
res40: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

scala> n |+| b
res39: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(2)

scala> n |+| n
res41: scalaz.@@[Option[Int],scalaz.Tags.First] = None

scala> List(a, b, c).sumr
res48: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

scala> List(n, b, c).sumr
res38: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(2)

scala> List(a, n, c).sumr
res55: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

scala> List(a, b, n).sumr
res56: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)
Tagで型を指定

First.Tagの指定をTagメソッド(scalaz.Tagのapplyメソッド)で行うこともできます。

val a = Tag[Option[Int], Tags.First](1.some)
val b = Tag[Option[Int], Tags.First](2.some)
val c = Tag[Option[Int], Tags.First](3.some)
val n = Tag[Option[Int], Tags.First](none)
FirstOption

OptionのTags.First用に専用の型としてFirstOptionが定義されています。Scalaz 6のFirstOptionは専用のトレイトでしたが、似たような使い方ができます。

val a: FirstOption[Int] = Tag(1.some)
val b: FirstOption[Int] = Tag(2.some)
val c: FirstOption[Int] = Tag(3.some)
val n: FirstOption[Int] = Tag(none)
firstメソッド

Scalaz 7のOptionはFirstOptionを取得するfirstメソッドを提供しています。Scalaz 6のfstメソッドに対応します。

val a = 1.some.first
val b = 2.some.first
val c = 3.some.first
val n = none[Int].first

Tags.Last

次はTags.Lastのタグ付けをした場合です。

val a: Option[Int] @@ Tags.Last = Tag(1.some)
val b: Option[Int] @@ Tags.Last = Tag(2.some)
val c: Option[Int] @@ Tags.Last = Tag(3.some)
val n: Option[Int] @@ Tags.Last = Tag(none)

Monoid演算の結果は以下になります。Monoid演算の結果、最後に有効なOptionが選択されていることが分かります。

scala> a |+| b
res73: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(2)

scala> a |+| n
res74: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(1)

scala> n |+| b
res75: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(2)

scala> n |+| n
res76: scalaz.@@[Option[Int],scalaz.Tags.Last] = None

scala> List(a, b, c).sumr
res77: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(3)

scala> List(n, b, c).sumr
res78: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(3)

scala> List(a, n, c).sumr
res79: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(3)

scala> List(a, b, n).sumr
res80: scalaz.@@[Option[Int],scalaz.Tags.Last] = Some(2)
Tagで型を指定

Last.Tagの指定をTagメソッド(scalaz.Tagのapplyメソッド)で行うこともできます。

val a = Tag[Option[Int], Tags.Last](1.some)
val b = Tag[Option[Int], Tags.Last](2.some)
val c = Tag[Option[Int], Tags.Last](3.some)
val n = Tag[Option[Int], Tags.Last](none)
LastOption

OptionのTags.Last用に専用の型としてLastOptionが定義されています。Scalaz 6のLastOptionは専用のトレイトでしたが、似たような使い方ができます。

val a: LastOption[Int] = Tag(1.some)
val b: LastOption[Int] = Tag(2.some)
val c: LastOption[Int] = Tag(3.some)
val n: LastOption[Int] = Tag(none)
lastメソッド

Scalaz 7のOptionはLastOptionを取得するlastメソッドを提供しています。Scalaz 6のsndメソッドに対応します。

val a = 1.some.last
val b = 2.some.last
val c = 3.some.last
val n = none[Int].last

Tags.Max

次はTags.Firstのタグ付けをした場合です。

val a: Option[Int] @@ Tags.Max = Tag(1.some)
val b: Option[Int] @@ Tags.Max = Tag(2.some)
val c: Option[Int] @@ Tags.Max = Tag(3.some)
val n: Option[Int] @@ Tags.Max = Tag(none)

Monoid演算の結果は以下になります。Monoid演算の結果、最大値の値を持つOptionが選択されていることが分かります。

scala> a |+| b
res81: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(2)

scala> n |+| b
res82: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(1)

scala> a |+| n
res83: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(2)

scala> n |+| n
res84: scalaz.@@[Option[Int],scalaz.Tags.Max] = None

scala> List(a, b, c).sumr
res85: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(3)

scala> List(n, b, c).sumr
res86: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(3)

scala> List(a, n, c).sumr
res87: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(3)

scala> List(a, b, n).sumr
res88: scalaz.@@[Option[Int],scalaz.Tags.Max] = Some(2)
Tagで型を指定

Fisrt.Tagの指定をTagメソッド(scalaz.Tagのapplyメソッド)で行うこともできます。

val a = Tag[Option[Int], Tags.Max](1.some)
val b = Tag[Option[Int], Tags.Max](2.some)
val c = Tag[Option[Int], Tags.Max](3.some)
val n = Tag[Option[Int], Tags.Max](none)
MaxOption

OptionのTags.Max用に専用の型としてMaxOptionが定義されています。

val a: MaxOption[Int] = Tag(1.some)
val b: MaxOption[Int] = Tag(2.some)
val c: MaxOption[Int] = Tag(3.some)
val n: MaxOption[Int] = Tag(none)

OptionのTags.Max向けに、firstメソッドに対応するメソッドはないようです。

Tags.Min

次はTags.Minのタグ付けをした場合です。

val a: Option[Int] @@ Tags.Min = Tag(1.some)
val b: Option[Int] @@ Tags.Min = Tag(2.some)
val c: Option[Int] @@ Tags.Min = Tag(3.some)
val n: Option[Int] @@ Tags.Min = Tag(none)

Monoid演算の結果は以下になります。Monoid演算の結果、最も小さな値としてSome(1)またはNoneが選択されていることが分かります。OptionのTags.Minは、最小値として値が設定されないNoneを選択する仕様になっているようです。ボクの直感ではNoneでない場合は、Some(1)を最小値とすると考えていたので、個人的に注意が必要な仕様と認識しました。

scala> a |+| b
res89: scalaz.@@[Option[Int],scalaz.Tags.Min] = Some(1)

scala> n |+| b
res90: scalaz.@@[Option[Int],scalaz.Tags.Min] = None

scala> a |+| n
res91: scalaz.@@[Option[Int],scalaz.Tags.Min] = None

scala> n |+| n
res92: scalaz.@@[Option[Int],scalaz.Tags.Min] = None

scala> List(a, b, c).sumr
res93: scalaz.@@[Option[Int],scalaz.Tags.Min] = None

scala> List(n, b, c).sumr
res94: scalaz.@@[Option[Int],scalaz.Tags.Min] = None

scala> List(a, n, c).sumr
res95: scalaz.@@[Option[Int],scalaz.Tags.Min] = None

scala> List(a, b, n).sumr
res96: scalaz.@@[Option[Int],scalaz.Tags.Min] = None
Tagで型を指定

Min.Tagの指定をTagメソッド(scalaz.Tagのapplyメソッド)で行うこともできます。

val a = Tag[Option[Int], Tags.Min](1.some)
val b = Tag[Option[Int], Tags.Min](2.some)
val c = Tag[Option[Int], Tags.Min](3.some)
val n = Tag[Option[Int], Tags.Min](none)
MinOption

OptionのTags.Min用に専用の型としてMinOptionが定義されています。

val a: MinOption[Int] = Tag(1.some)
val b: MinOption[Int] = Tag(2.some)
val c: MinOption[Int] = Tag(3.some)
val n: MinOption[Int] = Tag(none)

OptionのTags.Min向けに、firstメソッドに対応するメソッドはないようです。

ノート

関数型プログラミングのポイントの一つは、アルゴリズムを様々なデータ構造に対していかに再利用していくのかという点にあると思います。別の言い方をするとアルゴリズムとデータ構造を疎結合するためのメカニズムが論点になります。

Monoidという抽象および型クラスによる実行メカニズムは、Monoidを操作するアルゴリズムを、操作対象のデータ構造から疎結合にするために大きく寄与します。特にfold系の畳込みとMonoidの組合せは、関数型プログラミングにおける最重要イディオムの一つということができるでしょう。

ここまでがScalaz 6の成果ですが、Scalaz 7ではTagというメカニズムが加わり、さらにアルゴリズムを再利用できる範囲が広がりました。OptionのようなコンテナをMonoidとして使用する場合、コンテナに格納されているオブジェクトに対するMonoid演算は複数の可能性が考えられます。Scalaz 6のMonoidではその中の一つの選択(オブジェクト同士をMonoid演算する)に決め打ちにしていたわけですが、Scalaz 7ではTagの導入により、Tagの指定で選択を細かく指定することができるようになりました。

アルゴリズム側は全く意識することなく、Monoidのパラメータを指定するときのTag付けのみで、Monoid演算の細かな振舞いを切り替えることができます。このメカニズムにより、Monoidを操作対象とするアルゴリズムの適用範囲が更に広がることになります。

追記(2012-08-28)

Kenji YoshidaさんからMaxOptionとMinOptionが存在している旨の、ご指摘あったので、内容を修正しました。どうもありがとうございます。

また、ねこはるさんからご指摘のあったTags.First, Tags.Last, Tags.Max, Tags.Minメソッドは「Option(16) - First, Last, Max, Min その2」で情報を追加しました。

諸元

  • Scala 2.10.0-M6
  • Scalaz 7.0.0-M2

2012年8月24日金曜日

Scala Tips / Option(14) - Tag

今回から使用するScalaのバージョンを2.10.0-M6、Scalazのバージョンを7.0.0-M2に上げました。

今後はScala 2.10 & Scalaz 7でのベストプラクティスという観点で調べていく予定です。

さて、「Option (12) - Monoid」では、OptionがMonoidであることを説明しました。演算子|+|でMonoid演算を行うことができます。

OptionのようなコンテナがMonoidである場合、問題となるのは格納されているオブジェクトへの演算をどのようなセマンティクスで行うのかという点です。

Optionでのデフォルトでは、格納されたオブジェクトがMonoidであることを前提に格納されたオブジェクト間のMonoid演算の結果を、結果のSomeに格納します。

しかし、その他にも「Option (13) - Monoid 短絡OR」に説明した短絡OR的な演算も可能です。Scalaz 6では、トレイトFirstOption、LastOptionを追加し、Optionにfstメソッド、lstメソッドを追加することでOptionのMonoid演算時の短絡ORをサポートしていました。

一方、Scalaz 7ではTagというメカニズムによって短絡ORを実現します。

Tags.First

最初の要素を有効とする短絡OR用のTagとしてTags.Firstが定義されています。

OptionにTags.Firstを「@@」(こういうのは型演算子というのかな。未調査です。)でタグ付けした型を使用します。

まず2つのタグ付したOptionを変数a, bにそれぞれ設定します。タグ付けした場合は型が変わるので「Tag(1.some)」という形で型の調整を行う必要があります。

scala> val a: Option[Int] @@ Tags.First = Tag(1.some)
a: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

scala> val b: Option[Int] @@ Tags.First = Tag(2.some)
b: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(2)

この変数a, bをMonoid演算すると以下のように短絡OR演算が行われSome(1)が結果として返ってきます。

scala> a |+| b
res20: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

変数nにタグ付けしたNoneを設定します。

scala> val n: Option[Int] @@ Tags.First = Tag(none)
n: scalaz.@@[Option[Int],scalaz.Tags.First] = None

Monoid演算のいずれかにNoneが入る場合は、Some側が有効になります。両方Noneの場合は結果もNoneになります。

scala> a |+| n
res27: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

scala> n |+| b
res28: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(2)

scala> n |+| n
res29: scalaz.@@[Option[Int],scalaz.Tags.First] = None

畳込み

タグ付したOptionの集まりに対して畳込みを行うと、MonoidとTagの組合せの効果が分かります。

まず変数cに新しいタグ付けしたSomeを設定します。

scala> val c: Option[Int] @@ Tags.First = Tag(3.some)
c: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(3)

変数a, b, cのListに対してsuml(左からのMonoid畳込み)およびsumr(右からのMonoid畳込み)を行うといずれも結果はSome(1)となりました。

scala> List(a, b, c).suml
res21: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

scala> List(a, b, c).sumr
res22: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(1)

先頭の要素をNoneにした場合は、結果は最初のSomeであるSome(2)となりました。

scala> List(n, b, c).suml
res30: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(2)

scala> List(n, b, c).sumr
res26: scalaz.@@[Option[Int],scalaz.Tags.First] = Some(2)

諸元

  • Scala 2.10.0-M6
  • Scalaz 7.0.0-M2

2012年8月23日木曜日

Scala Tips / Monoid Index

そろそろScala 2.10.0がリリースされそうなので、調査の対象をScala 2.10.0 & Scalaz 7に移行しようと思います。そこでMonoidも一旦〆ることにしました。

Monoidは、他の項目でいろいろな形で説明してきているので、Monoidの項目で直接取り上げるのは2つのみになっています。

項目内容
Monoid - 新規作成Scalaz型クラスMonoidの型クラスインスタンスを実装する方法
Monoid (2) - 中央値中央値をMonoid化する

他の項目のところでは以下のとおり再三Monoidが登場しています。

Monoidは関数型プログラミングでの重要構成要素の一つだと思うので、どこかの時点でMonoid中心の視点でまとめ直してみたいと思います。

既存Index

以下は既存のIndexです。

Validation

項目内容
Validation (1) - 値の取り出し正常値またはデフォルト値を取得
Validation (2) - 関数で値の取り出しfoldを使って値を取り出す
Validation (3) - NonEmptyListFailure側をNonEmptyListにする
Validation (4) - mapFunctorとして使う
Validation (5) - flatMapMonadとして使う
Validation (6) - ExceptionExceptionをValidation化する
Validation (7) - forfor式で使う
Validation (8) - forで演算の連結for式でValidation結果を連結する
Validation (9) - forで演算を連結2for式で連結。前段を参照する場合。
Validation (10) - applicativeApplicative Functorとして使う
Validation (11) - モナドScalaモナドとScalazモナド
Validation (12) - parseparseIntなどでValidationを取得
Validation (13) - toValidatationFunction1のtoValidation
Validation (14) - オブジェクトの生成Validationしてオブジェクトを作る
Validation (15) - 多重度1の部品多重度1の部品の捌き方
Validation (16) - 多重度1の実装多重度1の部品の実装
Validation (17) - 多重度0または1の部品多重度0または1の部品の実装
Validation (18) - 多重度0以上の部品多重度0以上の部品の実装
Validation (19) - 多重度1以上の部品多重度1以上の部品の実装
Validation (20) - Personの実装色々な多重度のプロパティを持つPersonクラスを実装
Validation (21) - traversetraverseでValidationを扱う
Validation (22) - sequencesequenceでValidationを扱う
Validation (23) - foldfoldでValidationを扱う
Validation (24) - fold scalazscalazのfoldでValidationを扱う
Validation (25) - fold monoidMonoidを正常値とするValidationとfold
Validation (26) - fold monoid or↑でコンテナをor演算
Validation (27) - fold or↑で正常値がMonoidでない場合
Validation (28) - fold ListListに対する畳込み
Validation (29) - FoldableFoldable上でValidationを使う
Validation (30) - foldまとめValidationの畳込み処理のまとめ
Validation (31) - Function1Function1にあるValidation系メソッド
Validation (32) - Function0Function0にあるValidation系メソッド
Validation (33) - reduce
Validation (34) - SemigroupValidationをSemigroup化する
Validation (35) - MonoidValidationをMonoid化する
Validation (36) - ReducerValidationのReducerを作る

Validationの一連の記事では、Validationを素材にScalaやScalazの基本機能の使い方を調べるという意味合いも強くなっています。

以下はこの流れからスピンアウトして単独の記事にしたものです。

またValidation記事の中にも、基本機能の説明が主になっているものもあります。

Reducer

項目内容
ReducerIntProductReducerを素材にReducerの基本的な使い方
Reducer (2) - ListListReducerを素材に非可換MonoidでのReducerの振舞い
Reducer (3) - モノイド化非Monoidに対してReducerでモノイド演算を行う
Reducer (4) - 自前Reducer自前でReducerを用意する
Reducer (5) - 演算Monoid汎用MonoidをReducerでドメインオブジェクトに結びつける
GeneratorGeneratorで畳込み戦略、汎用Monoid、ドメインオブジェクトを結びつける

Reducerの操作対象であるMonoidについて以下の記事が前提となります。

IntProductReducerで使っているMonoidであるIntMultiplicationは以下の記事で説明しています。

Either

項目内容
Either値の取得
Either (2) - flatMapflatMapで文脈の切替
Either (3) - getOrElsegetOrElseでデフォルト値を指定
Either (4) - getOrElse, FlatMapflatMapで変換後にgetOrElse
Either (5) - Exceptionscala.util.control.Exceptionで例外ハンドリング
Either (6) - Left, RightLeftとRightの生成
Either (7) - forfor式で変換
Either (8) - for, getOrElsefor式で変換後にgetOrElse
Either (9) - 二項演算二項演算の説明
Either (10) - 二項演算,AND「Either:AND」×「値:任意の関数で計算」
Either (11) - ApplicativeEitherに対するApplicative
Either (12) - Applicativeの記述方法Eitherに対するApplicative(続き)
Either (13) - 二項演算,AND,Monoid「Either:AND」×「値:Monoid」
Either (14) - 二項演算,AND,PlusEither:AND」×「値:Plus」
Either (15) - 二項演算,OR「Either:OR」×「値:任意の関数で計算」
Either (16) - 二項演算,OR,Monoid「Either:OR」×「値:Monoid」
Either (17) - 二項演算,OR,Plus「Either:OR」×「値:Plus」
Either (18) - Booleanと相互変換EitherとBooleanの相互変換
Either (19) - Optionと相互変換EitherとOptionの相互変換
Either (20) - Validationと相互変換EitherとValidationの相互変換
Either (21) - BifunctorEitherにBifunctorを適用

Option

項目内容
Option値の取得
Option (2)nullをOptionに持ち上げる方法
Option (3)map
Option (4)withFilter
Option (5)collect
Option (6)flatMap
Option (7)for式
Option (8)getOrElse
Option (9)withFilter, map, getOrElse
Option (10) - ExceptionflatMap, Exceptionハンドリング
Option (11) - Some/NoneSomeとNoneの記述方法
Option (12) - MonoidOptionをMonoidとして使う
Option (13) - Monoid 短絡ORFirstOption/LastOptionで短絡OR |
nullnullを値に持ち上げる方法

Optionについては以下の記事も参考になると思います。

追記(2012-08-27)

Option(5)〜Option(11)までリンク切れしていたので修正しました。

2012年8月22日水曜日

Scala Tips / Validation Index

Validationもだいたいやり尽くしたので記事の一覧表をまとめておきます。

項目内容
Validation (1) - 値の取り出し正常値またはデフォルト値を取得
Validation (2) - 関数で値の取り出しfoldを使って値を取り出す
Validation (3) - NonEmptyListFailure側をNonEmptyListにする
Validation (4) - mapFunctorとして使う
Validation (5) - flatMapMonadとして使う
Validation (6) - ExceptionExceptionをValidation化する
Validation (7) - forfor式で使う
Validation (8) - forで演算の連結for式でValidation結果を連結する
Validation (9) - forで演算を連結2for式で連結。前段を参照する場合。
Validation (10) - applicativeApplicative Functorとして使う
Validation (11) - モナドScalaモナドとScalazモナド
Validation (12) - parseparseIntなどでValidationを取得
Validation (13) - toValidatationFunction1のtoValidation
Validation (14) - オブジェクトの生成Validationしてオブジェクトを作る
Validation (15) - 多重度1の部品多重度1の部品の捌き方
Validation (16) - 多重度1の実装多重度1の部品の実装
Validation (17) - 多重度0または1の部品多重度0または1の部品の実装
Validation (18) - 多重度0以上の部品多重度0以上の部品の実装
Validation (19) - 多重度1以上の部品多重度1以上の部品の実装
Validation (20) - Personの実装色々な多重度のプロパティを持つPersonクラスを実装
Validation (21) - traversetraverseでValidationを扱う
Validation (22) - sequencesequenceでValidationを扱う
Validation (23) - foldfoldでValidationを扱う
Validation (24) - fold scalazscalazのfoldでValidationを扱う
Validation (25) - fold monoidMonoidを正常値とするValidationとfold
Validation (26) - fold monoid or↑でコンテナをor演算
Validation (27) - fold or↑で正常値がMonoidでない場合
Validation (28) - fold ListListに対する畳込み
Validation (29) - FoldableFoldable上でValidationを使う
Validation (30) - foldまとめValidationの畳込み処理のまとめ
Validation (31) - Function1Function1にあるValidation系メソッド
Validation (32) - Function0Function0にあるValidation系メソッド
Validation (33) - reduce
Validation (34) - SemigroupValidationをSemigroup化する
Validation (35) - MonoidValidationをMonoid化する
Validation (36) - ReducerValidationのReducerを作る

Validationの一連の記事では、Validationを素材にScalaやScalazの基本機能の使い方を調べるという意味合いも強くなっています。

以下はこの流れからスピンアウトして単独の記事にしたものです。

またValidation記事の中にも、基本機能の説明が主になっているものもあります。

既存Scala Tips一覧

以下は既存のScala Tipsの一覧です。

Optionに以下の2項目を追加しました。

Reducer

項目内容
ReducerIntProductReducerを素材にReducerの基本的な使い方
Reducer (2) - ListListReducerを素材に非可換MonoidでのReducerの振舞い
Reducer (3) - モノイド化非Monoidに対してReducerでモノイド演算を行う
Reducer (4) - 自前Reducer自前でReducerを用意する
Reducer (5) - 演算Monoid汎用MonoidをReducerでドメインオブジェクトに結びつける
GeneratorGeneratorで畳込み戦略、汎用Monoid、ドメインオブジェクトを結びつける

Reducerの操作対象であるMonoidについて以下の記事が前提となります。

IntProductReducerで使っているMonoidであるIntMultiplicationは以下の記事で説明しています。

Either

項目内容
Either値の取得
Either (2) - flatMapflatMapで文脈の切替
Either (3) - getOrElsegetOrElseでデフォルト値を指定
Either (4) - getOrElse, FlatMapflatMapで変換後にgetOrElse
Either (5) - Exceptionscala.util.control.Exceptionで例外ハンドリング
Either (6) - Left, RightLeftとRightの生成
Either (7) - forfor式で変換
Either (8) - for, getOrElsefor式で変換後にgetOrElse
Either (9) - 二項演算二項演算の説明
Either (10) - 二項演算,AND「Either:AND」×「値:任意の関数で計算」
Either (11) - ApplicativeEitherに対するApplicative
Either (12) - Applicativeの記述方法Eitherに対するApplicative(続き)
Either (13) - 二項演算,AND,Monoid「Either:AND」×「値:Monoid」
Either (14) - 二項演算,AND,PlusEither:AND」×「値:Plus」
Either (15) - 二項演算,OR「Either:OR」×「値:任意の関数で計算」
Either (16) - 二項演算,OR,Monoid「Either:OR」×「値:Monoid」
Either (17) - 二項演算,OR,Plus「Either:OR」×「値:Plus」
Either (18) - Booleanと相互変換EitherとBooleanの相互変換
Either (19) - Optionと相互変換EitherとOptionの相互変換
Either (20) - Validationと相互変換EitherとValidationの相互変換
Either (21) - BifunctorEitherにBifunctorを適用

Option

項目内容
Option値の取得
Option (2)nullをOptionに持ち上げる方法
Option (3)map
Option (4)withFilter
Option (5)collect
Option (6)flatMap
Option (7)for式
Option (8)getOrElse
Option (9)withFilter, map, getOrElse
Option (10) - ExceptionflatMap, Exceptionハンドリング
Option (11) - Some/NoneSomeとNoneの記述方法
Option (12) - MonoidOptionをMonoidとして使う(2012-08-22追加)
Option (13) - Monoid 短絡ORFirstOption/LastOptionで短絡OR(2012-08-22追加) |
nullnullを値に持ち上げる方法

Optionについては以下の記事も参考になると思います。

2012年8月21日火曜日

クラウド温泉3.0 / Monadicプログラミング・マニアックス Index

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」関連記事のIndexです。

クラウド温泉

クラウド温泉全体の感想は以下になります。

スライド

Monadicプログラミング・マニアックスのスライドです。

記事一覧

「Monadicプログラミング・マニアックス」のスライドの作成過程のメモ記事の一覧です。

ノート

"関数型プログラミングはパイプライン・プログラミング"、と考えてみるとイメージも分かりやすいし、実際のプログラミング時のガイドとしても有効なので、その観点でまとめてみよう、というのが今回のセッションの趣旨です。

元々Haskell的、Scalaz的Monadicプログラミングをやろうと思ったのは、ScalazのPromiseを見て衝撃を受けたのがきっかけなので、PromiseとKleisliやApplicative Functorを組合せて並列プログラミングした効果を実測値付きで盛り込めて目標の一つは達成できたかなと思います。

実際に書き始めてみると、Monadicの話に行く前に今風の関数型プログラミングの基礎といったものが必要なことが分かったので、その部分を厚くしてみました。逆に、当初予定していたReducerを使ってMonoidを任意のドメインオブジェクトに接続する手法や、TraverseでKleisliをオブジェクトの集まりに適用する話題(これはミニセッションのテーマにもなった「Iteratorパターンの本質」とも関係してきます)、Validationを使ったMonadicプログラミングの具体例、といった話題は見送りました。

ボク自身もScalazを本格的に使い始めたのはここ一年ぐらいなので、まだまだ山の麓の所にいるわけですが、色々と追いかけていく方向性も見えてきたので、来年のクラウド温泉でもさらにマニアックな話題で何か発表できればと思っています。

2012年8月20日月曜日

クラウド温泉3.0

土日にクラウド温泉3.0@小樽に参加していました。今年のクラウド温泉もエキサイティングな内容で、とても楽しめました。幹事の中村さん(@nakayosix)、参加者の皆さん、どうもありがとうございました。

Monadicプログラミング・マニアックス

ボクは初日の最初の枠と2日目のフリーディスカッションの枠を担当しました。最初の枠で「Monadicプログラミング・マニアックス」というテーマでお話させていただきました。

内容はブログに書いてきたことをまとめたものになっています。PromiseモナドやApplicative Functorを使った並列プログラミングのコード例は参考になるかもしれません。

業務システムで使う型クラス

Scalaでは型クラスが使えるわけですが、オブジェクト指向のクラスインヘリタンスとかぶる機能でもあり使い方が難しい言語機能です。

神崎さんのこのセッションは、業務システムで型クラスを使用するトライアルについての報告で、とても参考になりました。

私見では、(Scalaの)型クラスの旨みは(1)コンパイル時に静的に多相性が解決されるためコンパイル時にエラー検出できる、(2)関数の選択が、主体、客体、文脈の組合せで行われる、(3)お互いに疎に開発した主体、客体をソースコードを変更することなく、型クラスインスタンスの追加(必要に応じてのみで接続することができる、といったところにあると考えています。

主体、客体を疎結合にできるため、部品の再利用性が高まることが期待できます。

ただし、(a)使うための決め事がちょっとある、(b)プラグインのような形でプログラムのバイナリを後付けで追加することはできない、という弱点もあります。

Scalaはオブジェクト指向言語なので、普通にちょっとした多相性が必要な時は、クラスインヘリタンスを使う方が圧倒的に楽です。逆に型クラスは気軽に定義して使うというような手軽さはないので、何か明確なメリットがある場合でないと採用しづらい技術です。

そういった点も含めてクラスと型クラスの使い分けというのを考える良いきっかけになりました。

ぶいてく流スケーラブルアプリの作り方 2012

時間があればKVSにBarkley DBを採用した理由、KVS特有のアプリケーション開発のデザインパターン(シャーディングのためのID管理、トランザクション境界の切り方、バッチ処理の組み方など)を議論したいところでした。かなり濃いメンバーが集まっていたので、製品紹介はパンフレット見てね、で終了して、冒頭から上記のようなテーマで議論に入るとより面白かったかもしれません。

LTタイム

LTタイムでは以下の発表が行われました。

  • 中村 良幸 (@nakayoshix) 「クラウド禅問答」
  • 鈴木 常彦 (@tss_ontap) 「IPv6の理想と現実〜若者に未来を託して〜」
  • @sumim「EclipseとPharo SmalltalkのTDD対決ムービーを副音声で解説!」
  • 小田 朋宏 (@tomooda) 「HaskellとSmalltalkの会話」「画面ゆらゆら系デモ」
  • 神林 飛志 (@okachimachiorz) 「Asakusa Framework 近況報告」

中村さんの「クラウド禅問答」はオフレコwなのでここでは触れないこととして、鈴木さんの「IPv6の理想と現実〜若者に未来を託して〜」はIPv6の現状がよくわかってとても参考になりました。IPv6といえば、1990頃にボクがメーカーのネットワーク部隊に居た頃、隣に居た人が担当していて色々やっていたのを思い出します。もう20年経ちますが、相当の前進はあったものの、未だに問題含みで実用には距離がありそうな感触でした。すでに稼働しているエコシステムを丸ごと取り替えるような新技術の普及はなかなか難しいですね。

Smalltalkはボクにとっては未知の分野ということもあって、@sumimさんと小田さんのセッションは興味深く聞けました。

プログラマのための代数入門2 Monadへの道

二日目の最初のコマは中村さんの代数入門「Monadへの道」です。代数の基本からのアプローチだったので、時間的に厳しかったかもしれません。ボク的にはScalaと圏論のマッピングのあたりから始めてもらえるとうれしかったわけですが、関数型をやっていない参加者のことを考えるとそうはいかないところですね。今回の収穫はWikipediaベースで調べていくのはあまりよくないということでした。

最後は定番の資料をベースに解説が行われました。(資料はリンクが分かり次第追加します。)

基礎から見直すTX処理〜A Critique of ANSI SQL Isolation Levelsを再読する

今回抜群に面白かったのが神林さんのTX解説です。

A Critique of ANSI SQL Isolation Levels」はDBの分野では超有名な論文とのことで、この論文を分かりやすく解説していただきました。「Transactional Information Systems」では第3章「Concurrency Control: Notions of Correctness for the Page Model」が相当するようです。

このTX解説を聞けただけで、クラウド温泉に参加した元は取れたという感じです。

本当は怖いDNSの話

鈴木さんの本当に怖い話。インフラのど真ん中のホラーなので、見なかったことにしておきたいwところです。

クラウドアプリを作る上では、こういったセキリティ技術も押さえておきたいので、技術的にもとても参考になりました。

Object Functionalをテーマとしたディスカッション

二日目の午後は、小樽商大の教室をお借りしてObject-Functionalについてフリーディスカッションしました。

ボクがネタとして出した資料は以下です。

やはりモナド周りが議論の中心になりました。

スライド中「関数型言語の技術マップ」で二点ほど修正案を頂いたので、これは持ち帰って考えてみたいと思います。

純粋関数型言語が今まであまり普及せず最近使われるようになってきた理由についてですが、最近の関数型言語はモナドの登場で状態(やIO)が記述できるようになったことが大きいのではないかという意見がありました。確かに、これは大きな理由かもしれません。

ディスカッションの続きとして急遽以下のミニセッションも行われました。

  • 「Iteratorパターンの本質」読書会
  • Haskelシューティングゲーム解説

『「Iteratorパターンの本質」読書会』は初日にボクが挙げた参考資料である「Iteratorパターンの本質」をスライドで見ながらディスカッションをしていくというものです。

この資料はMonadicプログラミングの重要な要素技術が網羅されているのでディスカッションの元ネタとしてとてもよい感じだったと思います。

「Haskellシューティングゲーム解説」は作者の@c255さんにHashkellで記述したシューティングゲームのソースコードを直接解説してもらうという贅沢なセッションでした。シューティングゲームが純粋関数型言語で作れるというのは驚きです。

Haskellを使うことで、プログラムの構造を大幅に変更しても、問題をコンパイラがエラーで弾いてくれるのでバグが出にくいということでした。

Scalaでも事情は同じで、リファクタリングした時の開発効率とバグの出にくさが静的型付けの大きなメリットだと思います。

まとめ

今回のクラウド温泉は、「代数スペシャル&Object Functionalとは」がテーマでした。

関数型言語もHaskell的なモナドベースのものは、圏論や群論といった代数的な操作がプログラミングの軸になります。Scala&Scalazも事情は同じですね。

また、クラウドアプリケーションで更に存在感が増すトランザクション技術においても、「基礎から見直すTX処理〜A Critique of ANSI SQL Isolation Levelsを再読する」にあったように代数的な操作による最適化が重要ということがわかりました。

クラウド時代のアプリケーション開発では、どうも代数が重要な要素技術、基盤技術の一つということになりそうというのが、今回の温泉のメッセージだったと総括できるように思います。

その上で、既存のオブジェクトの世界とどのようにつなげていくのかという議論に進んでいくわけですね。

今回も濃いメンバーの人たちと、クラウド、代数、オブジェクトについて色々と議論ができてとても楽しかったです。来年のクラウド温泉にもぜひ参加したいと思います。

2012年8月10日金曜日

クラウド温泉3.0 (18) / Promise

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その18です。

関数型プログラミングが期待されている大きな理由の一つは並列プログラミングです。今後プロセッサのコア数が急激に増えるのは確実で、複数コアを活用して高速に動作するプログラムを空気のように作ることが必須になります。

また、メモリ容量の増大やSSDの採用でデータベースもインメモリ化されてくると、従前のようなI/Oのボトルネックが解消してきます。このためアプリケーション側の並列処理にかかる比重が高まってくることになります。

このため、並列プログラミングは気合を入れて行う特別なものではなく、毎日のように接するコモディティとなるでしょう。普通に書くものが普通に並列動作するようになっていなくてはならないわけです。しかし、Javaなどの既存のオブジェクト指向言語では、並列プログラミングの難易度は高く、並列プログラミングのコモディティ化はとても達成できそうにありません。

関数型プログラミングの場合も並列プログラミングには色々なアプローチがありますが、有力なアプローチとなるのがモナドによる並列処理の隠蔽です。通常のMonadicプログラミングをしておくと、モナドの裏側で自動的に並列処理をしてくれます。この方法であれば、日常的に並列プログラミングしても、プログラマにかかる負担は多くありません。(そのかわりMonadicプログラミングをマスターするというハードルが追加されます。)

ScalazのPromiseは並列処理を隠蔽したモナドです。今回はPromiseによる並列プログラミングについて見ていきます。

準備

準備として動作時間を取得するための関数goを定義します。

def go[T](a: => T): (T, Long) = {
  val start = System.currentTimeMillis
  val r = a
  val end = System.currentTimeMillis
  (r, end - start)
}

テストに使用する関数fは引数に取ったInt値を100msウエイト後にそのまま返します。

val f = (x: Int) => {
  Thread.sleep(x * 100)
  x
}

f関数をgo関数上で動作させると以下になります。10×100msで一秒後に値10が返ってきています。

scala> go(f(10))
res176: (Int, Long) = (10,1000)

PromiseのKleisli

Monad活用のコツの一つはクライスリ関数です。Int値を取りMonadであるPromiseを返すクライスリ関数をKleisliでくるんだものを定義しておきます。

scala> val fp = f.promise
fp: scalaz.Kleisli[scalaz.concurrent.Promise,Int,Int] = scalaz.Kleislis$$anon$1@9edaab8

Applicative

まず普通にf(1)、f(2)、f(3)を順に足していく処理です。先頭から順に関数の実行時間も足し込まれるので600msかかります。

scala> go(f(1) + f(2) + f(3))
res212: (Int, Long) = (6,603)

PromiseのKleisliとApplicativeを使って同様の処理を行うと300msで実行できました。これは、fp(1)、fp(2)、fp(3)の処理が平行実行されたため、最も長いfp(3)の300msが全体の実行時間になったためです。

scala> go((fp(1) |@| fp(2) |@| fp(3))(_ + _ + _).get)
res215: (Int, Long) = (6,302)

ここで重要な点は、平行処理に対する同期処理はまったく記述していない点です。普通のMonadicプログラミングを行うだけで、Promiseモナドが自動的に同期処理を行なってくれます。

上記が普通のMonadicプログラミングというのは、以下のOptionに対する処理と見比べてみるとよくわかります。

scala> go((1.some |@| 2.some |@| 3.some)(_ + _ + _).get)
res237: (Int, Long) = (6,1)

つまり普通のMonadicプログラミングをすれば自動的に並列プログラミングになるわけです。Monadicプログラミングのハードルは高いものの、このハードルだけクリアしておけば自動的に並列プログラミングがついてきます。Javaで並列プログラミングを学習するコストと、実際のプログラミング時の手間やデバッグ効率を考えると、Monadicプログラミングのハードルの方がはるかに低いといえます。

Listに対する並列プログラミング

Listに対して普通に関数fを適用すると、関数fの実行時間が全て足し込まれたものが全体の処理時間になります。以下では1.2秒かかっています。

scala> go(List(1, 2, 3).map(f).map(f))
res221: (List[Int], Long) = (List(1, 2, 3),1205)

Promise(+Kleisli)を使うと以下のように600msで処理が完了しました。一段目のmap処理の300msと二段目のmap処理(内部でflatMap)の300msを足して600msになります。

scala> go(List(1, 2, 3).map(fp).map(_.flatMap(fp)).sequence.get)
res220: (List[Int], Long) = (List(1, 2, 3),602)

Kleisli関数の合成

Kleisliの威力を見るために、もう少し処理を複雑にしてみます。

関数fと連続実行した時に処理の合計が400msになる関数fxを定義します。同時にこれをPromiseのKleisli化したものも定義します。

val fx = (x: Int) => {
  val t = math.abs(x - 4)
  Thread.sleep(t * 100)
  x
}

val fxp = fx.promise

関数fと関数fxをListに対して普通にmapで連続実行した場合ですが、以下のように1.2秒かかりました。

scala> go(List(1, 2, 3).map(f).map(fx))
res223: (List[Int], Long) = (List(1, 2, 3),1205)

関数fと関数fxを合成した場合も1.2秒は変わりません。

scala> go(List(1, 2, 3).map(f >>> fx))
res232: (List[Int], Long) = (List(1, 2, 3),1205)

関数fと関数fxをPromiseのKleisli化した関数fpと関数fxpを連続実行したものが以下になります。内部で並列処理が行われ400msで実行が完了しました。

scala> go(List(1, 2, 3).map(fp).map(_.flatMap(fxp)).sequence.get)
res222: (List[Int], Long) = (List(1, 2, 3),402)

また、Klisliのfpとfxpを合成したものをmapで適用しても同様に400msで実行が完了しました。

scala> go(List(1, 2, 3).map(fp >=> fxp).sequence.get)
res230: (List[Int], Long) = (List(1, 2, 3),402)

PromiseのKleisliでは、Kleisliをパイプラインの各段に適用しても、Kleisliを合成したものを一度に適用しても、同じように並列処理が行われました。どちらのアプローチでも同じように並列処理が行われるというのは、プログラミングのしやすさからは非常に重要な性質です。

バージョン

セッションで用いるScalaとScalazはそれぞれ2.9.2と6.0.4です。

ScalaとScalazとも新バージョン(Scala: 2.10, Scalaz: 7)がリリースされる直前になっています。Scala 2.10では並列プログラミング周りが大きく変更されます。Scalaz 7もこの影響を受けるものと考えられます。

このため、本セッションで扱う内容はすぐに古くなってしまうことが確実ですが、基本的な考え方は引き続き有効だと思います。

2012年8月9日木曜日

クラウド温泉3.0 (17) / SQL

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その17です。

「Monadicプログラミング・マニアックス」のネタを要素技術のボトムアップで積み上げてきましたが、なかなか本丸に到達しないままクラウド温泉も来週末に迫って来ました。

来週はお盆休みでブログはお休みする予定なので、今日と明日の二回は応用からのトップダウンでネタを整理したいと思います。

SQLと関数型言語

関数型プログラミンをやっていて感じるのは、SQLなどの問い合わせ言語と関数型プログラミングの相性がとてもよいことです。現状はScalaとSQLが直接接続できるというわけではありませんが、データフローの実現で関数型プログラムとSQLを組合せる場合、同じセマンティクスの土俵の上で一気通貫に処理を考えることができます。これは一般的に問い合わせ言語が一種の関数型言語であるのが理由だと思います。その上で、プラットフォーム上の制約を考えて実装をプログラムとSQLとに振り分けるという形になります。

このアーキテクチャ上ではORMはちょっと中途半端な感じで、問題を複雑化させる要因になるかと思います。

現状のORMの問題の一つは、実現がER図的な共通ドメイン・モデル側に寄りすぎていて、ユースケース・スコープのドメイン・モデルはうまくハンドリングできない点です。この問題も関数型プログラミング&SQLでうまくさばくことができます。

またORMの実現方式にもよりますが以下のような問題もあります。

  • 性能チューニングや処理の記述でSQLの力を十分に活かせない。
  • データベーススキーマの変更や業務の変更に弱い。

こういう問題があるので、今までもプロ筋はiBatis/MyBatisを好んで使う傾向があったと思います。

問い合わせ言語はSQLだけではなく、各種のNoSQLにもそれぞれの問い合わせ言語がありますし、XMLではXQueryだけでなくXSLT、XPathも一種の問い合わせ言語と考えることができます。いずれも関数型言語としてとらえなおしてみると、関数型プログラミングとの併用で面白いユースケースが見つかりそうです。

SQLと関数型言語の接続

問い合わせ言語としてのSQLの特徴の一つは、問い合わせ結果が表形式になるということです。問い合わせの結果得られる情報は色々な構造を持つわけですが、一つの表にそういった構造がエンコードされて入ってきます。これをデコードするのが、プログラム側の最初の処理になります。

エンコードされる情報は木構造だったり、グラフ構造だったりするわけですが、これをデコードするコーディングはイディオム化できます。このイディオムを覚えておけば、SQLと関数型プログラミングを自由自在に接続することができるわけです。

組織階層図

例題としてRDBMSの上の部門マスタから組織ツリーを生成するプログラムを考えてみましょう。以下のようなSQLで部門マスタから部門情報を取り出すとします。

select C.部門ID as 部門ID, C.部門名 as 部門名,
       P.部門ID as 親部門ID, P.部門名 as 親部門名
  from 部門マスタ as C
    left outer join 部門マスタ as P on C.親部門ID=P.部門ID

プログラム

プログラムはSQLのアクセスは省略して、SQLの結果がMapのListとして返ってきた所から始めることにします。PlayのAnormがこういったアクセス法の代表例です。

変数recordsにデータが格納されています。

package tryout

import scalaz._, Scalaz._

object SqlSample {
  val records = List(Map('部門ID -> 1,
                         '部門名 -> "営業統括",
                         '親部門ID -> None,
                         '親部門名 -> None),
                     Map('部門ID -> 11,
                         '部門名 -> "東日本統括",
                         '親部門ID -> Some(1),
                         '親部門名 -> "営業統括"),
                     Map('部門ID -> 12,
                         '部門名 -> "西日本統括",
                         '親部門ID -> Some(1),
                         '親部門名 -> "営業統括"),
                     Map('部門ID -> 13,
                         '部門名 -> "首都圏統括",
                         '親部門ID -> Some(1),
                         '親部門名 -> "営業統括"),
                     Map('部門ID -> 111,
                         '部門名 -> "北海道支店",
                         '親部門ID -> Some(11),
                         '親部門名 -> "東日本統括"),
                     Map('部門ID -> 112,
                         '部門名 -> "東北支店",
                         '親部門ID -> Some(11),
                         '親部門名 -> "東日本統括"),
                     Map('部門ID -> 113,
                         '部門名 -> "北陸支店",
                         '親部門ID -> Some(11),
                         '親部門名 -> "東日本統括"),
                     Map('部門ID -> 114,
                         '部門名 -> "中部支店",
                         '親部門ID -> Some(11),
                         '親部門名 -> "東日本統括"),
                     Map('部門ID -> 121,
                         '部門名 -> "近畿支店",
                         '親部門ID -> Some(12),
                         '親部門名 -> "西日本統括"),
                     Map('部門ID -> 122,
                         '部門名 -> "中国支店",
                         '親部門ID -> Some(12),
                         '親部門名 -> "西日本統括"),
                     Map('部門ID -> 123,
                         '部門名 -> "四国支店",
                         '親部門ID -> Some(12),
                         '親部門名 -> "西日本統括"),
                     Map('部門ID -> 124,
                         '部門名 -> "九州支店",
                         '親部門ID -> Some(12),
                         '親部門名 -> "西日本統括"),
                     Map('部門ID -> 125,
                         '部門名 -> "沖縄支店",
                         '親部門ID -> Some(12),
                         '親部門名 -> "西日本統括"),
                     Map('部門ID -> 131,
                         '部門名 -> "東京支店",
                         '親部門ID -> Some(13),
                         '親部門名 -> "首都圏統括"),
                     Map('部門ID -> 132,
                         '部門名 -> "北関東支店",
                         '親部門ID -> Some(13),
                         '親部門名 -> "首都圏統括"),
                     Map('部門ID -> 133,
                         '部門名 -> "南関東支店",
                         '親部門ID -> Some(13),
                         '親部門名 -> "首都圏統括"))

  def main(args: Array[String]) {
    val a = build部門(records)
    val b = buildTree(a)
    showTree(b)
  }

  def build部門(records: Seq[Map[Symbol, Any]]): Map[Int, 部門] = {
    records.foldRight(Map[Int, 部門]())((x, a) => {
      val 部門ID = x('部門ID).asInstanceOf[Int]
      val 部門名 = x('部門名).asInstanceOf[String]
      val 親部門ID = x('親部門ID).asInstanceOf[Option[Int]]
      val 親部門名 = x.get('親部門名).asInstanceOf[Option[String]]
      a + (部門ID -> 部門(部門ID, 部門名, 親部門ID, 親部門名))
    })
  }

  def buildTree(sections: Map[Int, 部門]): Tree[部門] = {
    def build(sec: 部門): Tree[部門] = {
      val children = sections collect {
        case (k, v) if v.親部門ID == sec.部門ID.some => v
      }
      node(sec, children.toStream.sortBy(_.部門ID).map(build))
    }

    build(sections(1))
  }

  def showTree(tree: Tree[部門]) {
    println(tree.drawTree(showA[部門]))
  }
}

case class 部門(部門ID: Int, 部門名: String,
                親部門ID: Option[Int], 親部門名: Option[String])
ドメイン・モデル

検索結果をアプリケーションのドメイン・モデルに写像するわけですが、ドメイン・オブジェクトとして「部門」を定義しました。

業務アプリケーションの場合、ドメイン・モデルとのインピーダンス・ミスマッチを極力排除するためにドメイン・モデルが日本語の場合はプログラムの識別子にも同じ日本語を使うべき、と考えているので業務アプリケーションらしくこのサンプルもそうしています。DDDにおけるユビキタス言語の実践の一つですね。

build部門メソッドはrecordsの値(SQLでの検索結果)から部門一覧のMapを作成します。このメソッドが、SQLの結果とドメイン・モデルを写像する前半分の処理になります。

build部門メソッドでは、関数型プログラミングでおなじみのfoldRightメソッドを使って、問い合わせ結果を部門のMapに畳み込んでいます。Mapに対する畳込みがこの際のイディオムですね。

木構造の実現

buildTreeメソッドは部門一覧の表から部門の木構造を生成します。このメソッドが、SQLの結果とドメイン・モデルを写像する後半分の処理になります。

部門ツリーはScalazの代表的な永続データ構造であるTreeを用いて木構造を構築しています。

木構造の表示

部門の木構造の表示はshowTreeメソッドで行なっています。ScalazのTreeの機能を用いるので簡単に表示を行うことができます。

実行

プログラムを実行すると、以下のように組織ツリーが表示されました。

部門(1,営業統括,None,Some(None))
|
+- 部門(11,東日本統括,Some(1),Some(営業統括))
|  |
|  +- 部門(111,北海道支店,Some(11),Some(東日本統括))
|  |
|  +- 部門(112,東北支店,Some(11),Some(東日本統括))
|  |
|  +- 部門(113,北陸支店,Some(11),Some(東日本統括))
|  |
|  `- 部門(114,中部支店,Some(11),Some(東日本統括))
|
+- 部門(12,西日本統括,Some(1),Some(営業統括))
|  |
|  +- 部門(121,近畿支店,Some(12),Some(西日本統括))
|  |
|  +- 部門(122,中国支店,Some(12),Some(西日本統括))
|  |
|  +- 部門(123,四国支店,Some(12),Some(西日本統括))
|  |
|  +- 部門(124,九州支店,Some(12),Some(西日本統括))
|  |
|  `- 部門(125,沖縄支店,Some(12),Some(西日本統括))
|
`- 部門(13,首都圏統括,Some(1),Some(営業統括))
   |
   +- 部門(131,東京支店,Some(13),Some(首都圏統括))
   |
   +- 部門(132,北関東支店,Some(13),Some(首都圏統括))
   |
   `- 部門(133,南関東支店,Some(13),Some(首都圏統括))

2012年8月8日水曜日

クラウド温泉3.0 (16) / Kleisli

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その16です。

関数型プログラミングでは、引数が1つの関数が非常に重要です。極端に言うと引数が1つの関数を合成して、プログラムを構築していきます。

もちろん引数が2以上の関数も使いますが、この場合はカリー化や部分適用を使って引数が1つの関数に持っていくのが重要なテクニックになっています。

引数の数が1の関数

引数の数が1つの関数として、3倍する関数mul3と5を加える関数plus5を定義します。

val mul3 = (_: Int) * 3
val plus5 = (_: Int) + 5

引数1の関数は以下のようにmapメソッドといった高階関数に指定して処理を合成することができます。多くの高階関数は引数の数が1の関数を入力にするので、引数の数が1の関数は再利用可能な部品として利用価値が高くなります。

scala> List(1, 2, 3).map(mul3)
res19: List[Int] = List(3, 6, 9)

A→M[B]

FunctorのmapメソッドはA→Bという形の関数を引数に取りますが、MonadのflatMapはA→M[B]という形の関数を引数に取ります。MはMonadなので、AからBをMonadでくるんだものを返す関数ということになります。A→M[B]も引数の数が1の関数の一種ですが、Monadを返すところが特殊化されています。

Monadは関数型プログラミングに非常に重要な構成要素です。つまり、A→M[B]の関数も再利用可能な部品として利用価値が高いと考えられます。

先ほど定義したmul3とplusをMをListとしてA→M[B]化した関数mul3lとplus5lは以下になります。狙いが分かりやすいようにmul3lは0以上の値、plus5lは偶数を有効として扱うように機能追加しました。

val mul3l = (x: Int) => if (x >= 0) List(x * 3) else Nil
val plus5l = (x: Int) => if (x % 2 == 0) List(x + 5) else Nil

mul3lをListのmapメソッドに適用すると以下のようになります。

scala> List(1, -2, 3, 4).map(mul3l)
res33: List[List[Int]] = List(List(3), List(), List(9), List(12))

さて、mul3lをListのflatMapメソッドに適用すると以下になります。-2に対応する要素が結果から取り除かれていますが、これがモナドの効果です。

scala> List(1, -2, 3, 4).flatMap(mul3l)
res34: List[Int] = List(3, 9, 12)

mul3lとplus5lを連続してflatMapで適用すると以下になります。

scala> List(1, -2, 3, 4).flatMap(mul3l).flatMap(plus5l)
res32: List[Int] = List(17)

Kleisli

mul3lとplus5lの組み合わせが頻出する場合、これを一つの関数に合成しておくと便利です。ただし、mul3lやplus5lは引数と返却値の型が違うので単純な関数合成はできません。

scala> mul3l andThen plus5l
<console>:16: error: type mismatch;
 found   : Int => List[Int]
 required: List[Int] => ?
              mul3l andThen plus5l
                            ^

そこで登場するのがKleisliです。

"プログラムとはクライスリ圏の射である(program is arrow of Kleisli category)"という背景があり、クライスリ圏の射に対する演算をプログラミング言語で扱うメカニズムがScalaやHaskllのモナドということになるかと思います。

そして、クライスリ圏の射はScalaではA→M[B]の関数が対応します。A→M[B]の関数はMonadのflatMapメソッドに適用できますが、単体で扱いたい場合には特別なサポートがあると便利です。このような目的で使用するのがScalazのKleisli(以下単にKleisliと呼びます)です。

KleisliはA→M[B]の関数をくるんで、クライスリ圏の射として操作するためのメカニズムを提供します。

mul3lとplus5lをKleisli化すると以下になります。mul3lkとplus5lkがKleisli化したmul3lとplus5lです。

scala> val mul3lk = kleisli(mul3l)
mul3lk: scalaz.Kleisli[List,Int,Int] = scalaz.Kleislis$$anon$1@6119d27b

scala> val plus5lk = kleisli(plus5l)
plus5lk: scalaz.Kleisli[List,Int,Int] = scalaz.Kleislis$$anon$1@38ab3c51

mul3lkとplus5lkはKleisliなので演算子>=>で合成することができます。

scala> val m3p5lk = mul3lk >=> plus5lk
m3p5lk: scalaz.Kleisli[List,Int,Int] = scalaz.Kleislis$$anon$1@76f4a74b

scala> m3p5lk(1)
res40: List[Int] = List()

scala> m3p5lk(4)
res41: List[Int] = List(17)

合成したKleisliであるm3p5lkをflatMapに適用すると以下のようになります。このm3p5lkのようにA→M[B]をKleisli化して合成することでMonad向けの部品を整備することができます。

scala> List(1, -2, 3, 4).flatMap(m3p5lk)
res39: List[Int] = List(17)

なおKleisliは引数1の関数でもあるのでmapメソッドに適用することもできます。

scala> List(1, -2, 3, 4).map(m3p5lk)
res43: List[List[Int]] = List(List(), List(), List(), List(17))

パイプライン・プログラミング

Kleisliをパイプライン・プログラミングの観点でみてみましょう。

まずListからmapやflatMapメソッドを適用する形も処理が左から右に流れていくのでパイプライン・プログラミングといえます。これがMonadを使ったパイプライン・プログラミングです。

scala> List(1, -2, 3, 4).flatMap(mul3lk).flatMap(plus5lk)
res46: List[Int] = List(17)

さらに関数の合成としてのパイプライン・プログラミングとしてはパイプライン演算子とKleisliの合成を使って以下のような処理が可能です。

scala> 4 |> mul3lk >=> plus5lk
res45: List[Int] = List(17)

2012年8月7日火曜日

クラウド温泉3.0 (15) / Arrow

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その15です。

今回のテーマはArrowです。Arrowは圏論の射(arrow, morphism)を型クラス化したもので、圏(category)を型クラス化したCategoryと組合せて圏論的な操作ができると思われますが、具体的なユースケースはボクもよくわかりません。

しかし、引数1の関数であるFunction1を合成するという用途では一定の利用価値があります。

Scalazでの型クラスは定義は以下のような形になります。

関数の合成

まず3倍する関数mul3と5を加える関数plus5を定義します。

scala> val mul3 = (_: Int) * 3
mul3: Int => Int = <function1>

scala> val plus5 = (_: Int) + 5
plus5: Int => Int = <function1>

Scala純正のcomposeメソッドで合成することができます。

scala> val p5m3 = mul3 compose plus5
p5m3: Int => Int = <function1>

scala> p5m3(3)
res147: Int = 24

Scala純正のandThenメソッドで合成することもできます。andThenメソッドの方が左側の関数→右側の関数の順に関数が実行されるので、よりパイプライン的です。ただandThenというメソッド名が少し重たい感じです。

scala> val p5m3 = plus5 andThen mul3
p5m3: Int => Int = <function1>

scala> p5m3(3)
res147: Int = 24

ScalazのArrowを使うと関数合成を以下のように>>>演算子で記述することができます。

scala> val p5m3 = plus5 >>> mul3
p5m3: Int => Int = <function1>

scala> p5m3(3)
res147: Int = 24

パイプライン演算子|>と組み合わせると以下のようになります。まさにパイプライン処理という感じですね。

scala> 3 |> plus5 >>> mul3
res150: Int = 24

パイプラインの複線化

&&&演算子でパイプラインを複線化、***演算子でパイプラインの並行実行を行うことができます。また、firstメソッドで一番目のパイプラインのみの実行、secondメソッドで二番目のパイプラインのみの実行を行うこともできます。

scala> 3 |> (plus5 &&& mul3)
res43: (Int, Int) = (8,9)

scala> 3 |> (plus5 &&& mul3) >>> (plus5 *** mul3)
res44: (Int, Int) = (13,27)

scala> 3 |> (plus5 &&& mul3) >>> (plus5 *** mul3) >>> mul3.first
res45: (Int, Int) = (39,27)

詳しくは「関数型とデータフロー(2)」を参照してください。

Monadicプログラミング

本セッションではMonadicプログラミングは以下のものを指すことにしました。

  • Monadを中心としつつも、旧来からあるFunctorによるプログラミング、関数合成、コンビネータなども包含しつつ、パイプライン・プログラミングのスタイルとしてまとめたもの。

Arrowによる関数合成も「パイプライン・プログラミング」であるMonadicプログラミングの重要な構成要素になります。

2012年8月6日月曜日

クラウド温泉3.0 (14) / Applicative

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その14です。

Functorよりも強くMonadよりも弱い性質を持つ型クラスにApplicativeがあります。正確にはApplicative Functorという名前がついていますが、型クラス名に合わせてここではApplicativeと呼ぶことにします。

圏論系の技術の中での位置付けは以下のような感じです。

Scalazでの型クラスは定義は以下のような形になります。

Scala Tipsの中では以下の記事が分かりやすいかもしれません。

Applicative

ApplicativeはFunctorやMonadと同様にオブジェクトを格納するコンテナかつ計算文脈です。Applicativeの集まりに対して、1つのApplicativeを1つのパラメタとして演算を実行することができます。

演算子|@|がScalazが提供する代表的なApplicative演算子です。

scala> (1.some |@| 2.some)(_ + _)
res89: Option[Int] = Some(3)

ApplicativeのListに対して以下のようにfoldによる畳込みを行うことができます。

scala> List(1.some, 2.some).foldRight(0.some)((x, a) => (x |@| a)(_ + _))
res104: Option[Int] = Some(3)

ApplicativeはTraversableとの組み合わせで畳込み処理を行うこともできます。ApplicativeはTraversableの対象となっているのが重要な性質の一つです。

scala> List(1.some, 2.some).sequence
res92: Option[List[Int]] = Some(List(1, 2))

scala> List(1.some, 2.some).sequence.map(_.foldRight(0)(_ + _))
res111: Option[Int] = Some(3)

ListやOptionあるいはValidationなど、Monad的なオブジェクトはMonadかつApplicativeであることがほとんどなので、プログラミング上はMonadの機能の一つと考えておくと分かりやすいでしょう。

使用例

ApplicativeはValidationと併用するのが典型的な使用例です。

ノート

Applicativeで行う処理の多くは条件が合えばMonoidでも実現することができます。Monoidを使った方が簡潔になるので、可能であればMonoidを使用するのがよいでしょう。

scala> 1.some |+| 2.some
res88: Option[Int] = Some(3)

scala> List(1.some, 2.some).sequence.map(_.sum)
res96: Option[Int] = Some(3)

scala> List(1.some, 2.some).foldMap(identity)
res100: Option[Int] = Some(3)

scala> List(1.some, 2.some).sumr
res102: Option[Int] = Some(3)

ApplicativeとMonoidの使い分け、Monoidが使用できる条件判定をプログラミング時に瞬時に判断できるようにしておくのが大事です。

2012年8月3日金曜日

クラウド温泉3.0 (13) / Monad

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その13です。

Monadicプログラミングをテーマにしているので、主役はやはりMonadです。

Monadについては、Scala Tipsでも中心的なテーマとして取り上げてきたので、この中から今回のセッションの趣旨にそったものを集めてくる感じになります。

Monadicプログラミングをパイプライン・プログラミングと見立てると、以下のような記述方式が候補です。

この中でMonadを使っているのは3, 4, 5となります。パイプラインという観点からは、Monadを使っていないものを排除しても実用的なメリットはないので、合わせてMonadicプログラミングと呼ぶのが本セッションでの定義です。

Scalaにおけるモナドについては、以下の記事が参考になるかもしれません。

Monadの実装に用いられているScalazの型クラスは以下の記事で取り上げています。

Monadの具体的な使用方法として、以下のクラスを取り上げてきました。

モナドが提供する計算文脈を使うことで、計算文脈の提供するサービスを暗黙的に利用しながら動作する処理を、パイプラインのセマンティクスで処理を簡潔に記述することができます。

パイプラインの表層を流れるデータと演算、さらにパイプラインの裏側で暗黙に行われるデータと演算が疎結合になっているのが、モナドの醍醐味だと思うので、そのあたりにフォーカスしていきたいと思います。

ノート

パイプライン・プログラミングという観点からは重要な切り口ではないので、セッションでは取り上げないと思いますが、Scala TipsではfoldとMonadの組み合わせについても取り上げています。その他、Monadを対象としたメソッドが色々あるので、Scala Tipsで随時取り上げていきたいと思います。

2012年8月2日木曜日

クラウド温泉3.0 (12) / Monoid

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その12です。

関数型プログラミングで最重要な構成要素としてまずあげられるのがFunctorです。具体的にはmapメソッドを提供しているコンテナが相当します。これは空気のようなものですね。

そして、言うまでもなく最近の話題の中心はMonad。Scalaも言語仕様に取り入れています。セッションのテーマであるMonadicプログラミングも、このMonadをいかに活用するのかというのがひとつの軸になります。

この2つに加えて、個人的にこれも最重要構成要素に入れてよいのではと考えているがMonoidです。

Monoidは関数型プログラミングに頻出する抽象です。閉じた2項演算(つまりA☆A→A)でMonoidの規則を満たすものがあれば、Monoidとして定義しておくことでMonoid向けのさまざまなロジックをそのまま適用することができます。その代表的なものの一つがfoldによる畳込みです。

Monoidは残念ながらScalaの基本ライブラリでは提供されていないので、ScalazのMonoidを使うことになります。Scalazでは型クラスMonoidを提供しており、任意の型をMonoidとして定義することができます。

今まで記事でMonoidについて様々な観点から取り上げてきたので、この中から情報をピックアップしてスライドに集約する形になります。どれを持ってくるか悩みどころですね。

fold

Monoidの重要なユースケースであるfoldについて。foldとMonoidの組み合わせは関数型プログラミングのハイライトの一つかもしれません。Monadicプログラミングという観点からも、一つスライドを割り当てて取り上げることになると思います。

Reducer/Generator

Monoidの応用であるReducer, Generatorについて。Reducer自身は利用するシーンはあまり多くはないかもしれませんが、Monoidをどのような形で伸ばしていくのかというアプローチは参考になります。

2012年8月1日水曜日

クラウド温泉3.0 (11) / reduce, collect, flatMap

クラウド温泉3.0@小樽のセッション「Monadicプログラミング・マニアックス」で使用するスライドのネタ検討その11です。

前回はFunctorが提供する計算文脈におけるパイプラインで使用できるmapメソッド、filterメソッド、foldメソッドを取り上げました。この3つが基本中の基本になりますが、他にも有用なものがあります。

reduce

reduceメソッドはコンテナ内の要素に対して二項演算を繰り返し適用し、ひとつの要素にまとめる処理を行います。foldメソッドの用途限定版です。

scala> List(1, 2, 3).reduce(_ + _)
res2: Int = 6

collect

collectメソッドは部分関数(partial function)を使って、要素の選択を変換を同時に行います。filterメソッドとmapメソッドを一つにまとめたような動きになります。

scala> List(1, 2, 3).collect {
     |  case x if x % 2 == 1 => x.toString
     | }
res5: List[java.lang.String] = List(1, 3)

flatMap

flatMapメソッドはモナドの動作を行うメソッドです。mapメソッドに指定する関数が要素の変換を行うのに対して、flatMapメソッドに指定する関数は要素からコンテナへの変換を行います。このコンテナの集まりを"コンテナの流儀"でひとつのコンテナにまとめるのがモナドの肝となる動きになります。

scala> List(1, 2, 3).flatMap(x => {
     | if (x % 2 == 1) List(x.toString, (x * 10).toString) else Nil
     | })
res7: List[java.lang.String] = List(1, 10, 3, 30)

Listの場合は、Listの集まりを順序を維持したまま一つのListにまとめます。このため、flatMapの実行でListの要素数を増やしたり減らしたりといったことが可能です。上記の例では、リストの要素数が3から4に増えています。

パイプラインの構成部品

パイプラインの構成部品の表にreduce, collect, flatMapを追加しました。

メソッド動作動作イメージコンテナ型要素型要素数
mapコンテナ内の要素に関数を適用して新しいコンテナに詰め直す。M[A]→M[B]変わらない変わる変わらない
filterコンテナ内の要素を選別して新しコンテナに詰め直す。M[A]→M[A]変わらない変わらない減る
foldコンテナをまるごと別のオブジェクトに変換する。M[A]→N変わる--
reduceコンテナの内容を一つにまとめる。M[A]→A変わる--
collectコンテナ内の要素に部分関数を適用して選択と変換を行う。M[A]→M[B]変わらない変わる減る
flatMapコンテナ内の要素ごとにコンテナを作成する関数を適用し最後に一つのコンテナにまとめる。M[A]→M[B]変わらない変わる増える/減る |

メソッドの追加に加えて、以下の点を変更しています。

  • コンテナ→コンテナ型、要素→要素型
  • コンテナ型が「変わる」の時は要素型、要素数は意味を持たないので「-」とした。

パイプラインで有用な部品のパターンは概ねカバーできたと思います。

ノート

スライド的には、この表で一つスライドを起こして、各メソッドを例題付きで簡単に説明する感じになりそうです。

foldとreduce

reduceはfoldの特殊な形なのでfoldファミリと考えるとよいでしょう。fold系の処理の中で条件によってreduceを選ぶという形になります。

foldファミリは、Monoidと相性が良いので、Monoidとどう組合せていくのかというのも重要な論点になります。これはMonoidで一つスライドを起こして説明したいと思います。

foldMap

flatMapメソッドは、まさにMonadicプログラミングの核となるメソッドです。

この比較表ではflatMapメソッドの威力はあまり見えてきません。これはFunctor視点の比較表になるためですね。flatMapについては別枠で説明することにすることになります。