2012年7月24日火曜日

クラウド温泉3.0 (5) / 永続データ構造としてのケースクラス

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

関数型言語における永続データ構造の運用イメージは「イミュータブルデータ構造は遅いような気がしていたが、別にそんなことはなかったぜ!」のキャッチが秀逸な「20分で分るPurely Functional Data Structures」がとてもいい資料です。

このような本格的な永続データ構造の実現はかなり難易度の高いプログラミングになりそうですが、有名所は専門家が開発したものがクラスライブラリに用意されているので、普通のプログラミングではそれを利用する技術を知っておけば十分です。

逆にアプリケーションのドメインオブジェクトを永続データ構造で実現する場合は、当然アプリケーション側で実装する必要があります。しかし、このような場合は難しいアルゴリズムは不要で、一定のイディオムを活用してサクサク作ってしまえばOKです。Scalaの場合は、性能が出なければ(難しいアルゴリズムは導入せず)そのままミュータブルにしてしまうという奥の手もあるので、最初の取っ掛かりは安全性重視でイミュータブルにしてしまうのがよいでしょう。

で、ドメインオブジェクトを永続データ構造で実現するときのアプローチの一つとしてケースクラスがあります。ケースクラスは「代数的データ型 on Scala」で説明した通り直積(Product)という側面もあり、sealed trait/abstract classと組合せて"直積の直和の総和"として代数的データ型を実現できます。

その一方で、シンプルな永続データ構造であればケースクラスで簡単に実現することができます。

人を表すPersonクラスを例にして実現方法を見ていきましょう。

定義

Personクラスを定義して、インスタンスを作成します。

scala> case class Person(name: String, age: Int)
defined class Person

scala> val taro = Person("Taro", 30)
taro: Person = Person(Taro,30)

永続データ構造として利用

永続データ構造に対する更新は、"「更新不可のオブジェクトを更新する」という矛盾を関数型プログラミングのテクニックで回避する"ことですが、基本的には更新部分の属性のみを変更した新しいオブジェクトを元のオブジェクトから複製することによって実現します。

基本となるアプローチはコンストラクタを使うもので、以下のようにコンストラクタに更新元のオブジェクトの属性値を並べて指定し、更新したい属性のみ新しい値を指定します。この場合はPersonの年齢を変更しています。

scala> val taro31 = Person(taro.name, 31)
taro31: Person = Person(Taro,31)
コピーコンストラクタ

コンストラクタですべてのパラメタを指定するのはプログラミングも大変ですし、デフォルトパラメタなどがある場合、パラメタ指定の抜けなどが発生してバグの元にもなります。このような問題を解決しているのがいわゆるコピーコンストラクタであるcopyメソッドです。

copyメソッドを使って、必要な属性のみを更新したオブジェクトを複製することができます。

scala> val taro31 = taro.copy(age = 31)
taro31: Person = Person(Taro,31)

永続データ構造的な機能追加

年齢の更新がPersonオブジェクトにとって、(1)使用頻度が高い、(2)ドメインモデル上重要な意味を持っている、(3)ドメイン特化のアルゴリズムを持っている、といった事情がある場合には、専用の更新メソッドを追加するのがよいアプローチです。

以下は年齢を加算するメソッドを追加したPersonです。こういった形で更新メソッドを定義し、実装にはcopyメソッドを使うのがイディオムです。

case class Person(name: String, age: Int) {
  def addAge(n: Int) = {
    copy(age = age + n)
  }
}

下のように使用します。

scala> taro.addAge(1)
res70: Person = Person(Taro,31)

ノート

以上で説明したようにケースクラスを用いると永続データ構造を簡単に作成することができます。ポイントとなるのはコピーコンストラクタで、変更対象となる属性のみを指定すると、その他の属性は自動的に設定された複製を作成してくれます。ケースクラスはこのコピーコンストラクタを自動的に定義してくれるのがとても重要な点です。

このように関数型プログラミングでの重要概念である代数的データ型と永続データ構造はどちらもScalaではケースクラスがキーとなる機能でした。

そういう意味で、代数的データ型/永続データ構造という枠組みの中でのケースクラスの活用がScalaプログラミングのコツということですね。

このあたりはScalaプログラミング的には非常に重要ですが、Monadicプログラミングからは距離があるので、セッション内でどの程度触れるのかは要検討です。

  • 代数的データ型&永続データ構造→ケースクラス

で一枚のスライドにまとめてしまうかもしれません。

0 件のコメント:

コメントを投稿