2012年10月31日水曜日

Scala Tips / ケースクラス設計原則

重厚な名前をつけてみましたが、内容はゆるふわです。

Scalaには色々な革新的な機能が導入されていますが、あえて日々のプログラミングで一番重要な機能は?と考えていくとケースクラスですよね。

ケースクラスは便利なので、なんにでも使いたくなってしまいますが、最近原則めいたことが分かってきたので、日々のプログラミング時の個人的な指針としています。

ケースクラスは直積

ケースクラスの利用方法を考える上で重要な指針となるのは、ケースクラスは「直積」という点です。Scalaでは直積はProductというトレイトで表現しますが、ケースクラスもこのProductが自動的にmixinされます。

また、直積に関連して代数的データ型という観点も重要です。このあたりの事情は「クラウド温泉3.0 (3) / 代数的データ型 on Scala」にも書きました。

原則

直積、代数的データ構造といった数学的な構成要素がケースクラスの土台になっているすると、ケースクラスではこの土台を揺るがすようなコーディングは慎むのが安全策です。

土台を揺るがす要因は以下の2点です。

  • 更新可能な変数を持っている
  • インヘリタンスで後付で振る舞いの変更が可能

これを避けるためには、ケースクラスは以下の使い方に留めるのが安全策となります。

  • ケースクラスは不変オブジェクトの実装に用いる。
  • インヘリタンスはsealedを用いて適用範囲を限定する。

つまり、ケースクラスは純粋関数型の範囲で使うということですね。

逆に、ケースクラス的な使い方をするクラスでも、不変オブジェクトではない場合はあえてケースクラスにしないというコーディングをします。こうすることによって、このコーディング方針を知っていれば、プログラムの意図が明確になりプログラムの可読性があがります。

以上、ゆるふわ指針でした。

2012年10月30日火曜日

Scala Tips / Option(18) - Boolean

昨日に引き続いてScalazの小ネタです。

OptionとBooleanの相互変換もよく出てくる処理です。普通に考えても簡単にできる処理ではありますが、より簡潔に書くことができればプログラミング効率も地味に上がってきます。

Option→Boolean

まず、OptionをBooleanに変換する処理です。OptionがSomeの場合はtrue、Noneの場合はfalseを得る処理になります。

この手の処理はmatch式が定番です。

o match {
  case Some(_) => true
  case None => false
}

しかし、Scalazを使うともう少し短くできそうです。

まずcataメソッド。

o.cata(x => true, false)

次はsomeメソッドとnoneメソッドのチェイン。

o.some(x => true).none(false)

少し短くすることができましたが、Someに格納されている値は無関係なのでもう少し短くしたいところです。

そこで登場するのが?メソッドと|メソッドのチェイン。

o ? true | false

これはかなり短くなりました。

と、ここでよく考えてみると、Scalazを使わないでもOptionにそのものズバリがありました。

o.isDefined

OptionからBooleanへの変換はOptionのisDefinedメソッドを使うのが結論です。

Boolean→Option

BooleanをOptionに変換するときには:

if (b) 1.some else none

とするのが普通ですが、Scalazを用いると以下のように書くこともできます。

b ? 1.some | none

いずれの場合も、「else none」や「| none」がちょっと悔しいですね。

Scalazだと以下のように書くことができます。

b option 1

optionメソッドはby-nameパラメタなので以下のように処理を記述することもできます。この使い方は結構使い出があります。

b option {
  ...何かの処理
}

諸元

  • Scala 2.10.0-RC1
  • Scalaz 2.10.0-M6

2012年10月29日月曜日

Scala Tips / Option(17) - orEmpty, orZero

Scalaプログラミングの要諦はOptionにあり、と常々感じるわけですが、Scalazを使うとさらにOptionが便利になります。

先日は以下の様なワンライナーが決まり、ガッツポーズがでました。(?)

target.orEmpty[List] ::: ~deps

targetはOption[T]型、depsはOption[List[T]]型です。

基本的には、targetの内容のTとdepsの内容のList[T]をconsで接続して一つのListにしたいわけですが、targetとdepsのそれぞれがOption型でどちらもNoneである可能性があるのが厄介です。

Java的なコーディングで普通に書くと、targetがSomeとNoneの2通り、depsがSomeとNoneの2通りの組合せで、4通りの組合せに対してifやmatchを用いた分岐を用いることになりそうです。

これは結構煩雑な処理になりますが、orEmptyメソッドとorZeroメソッド(またはunary_〜メソッド)を用いると上記のように完結に記述することができるわけです。

orEmptyメソッド

OptionのorEmptyメソッドは以下の処理を行います。

  • OptionがSomeの場合、Someの内容を型パラメタで指定したコンテナに格納して返す
  • OptionがNoneの場合、型Tの零元を型パラメタで指定したコンテナに格納して返す

ポイントになるのは以下の2点です。

  • 型パラメタで指定したコンテナを自動的に生成
  • Noneの場合は型Tの零元を自動的に生成

この2つの処理を型情報に従って自動的に行なってくれるので、プログラミングを大幅に省略できるわけです。

プログラムに登場する「target.orEmpty[List]」はtargetがSome[Int]ならSomeの中に入っている数値を入れたList[Int]を、NoneならList(0)を返します。

orZeroメソッド(unary_〜メソッド)

Optionのunary_〜メソッドはorZeroメソッドと同じものです。

OptionのorZeroメソッドは以下の処理を行います。

  • OptionがSomeの場合、Someの内容を返す
  • OptionがNoneの場合、型Tの零元返す

こちらも以下の点がポイントです。

  • Noneの場合は型Tの零元を自動的に生成

ノート

零元を自動生成するというのはわりと重要な機能で、Scalazの型クラスZeroによって実現しています。Monoidの便利さも零元(単位元)によるところが大きいことは言うまでもありません。

Scalazを用いると、こういった形で処理を完結に書けるのがよいですね。

ScalazはMonoidやTraverseのような大物もよいのですが、OptionWやBooleanWといった小粒の機能がなかなか便利なのもよいところです。

諸元

  • Scala 2.10.0-RC1
  • Scalaz 2.10.0-M6

2012年10月26日金曜日

SimpleModeler最新状況

SimpleModelerはメタモデルの拡張も含めて、色々と改造しているため、最新版がどうなっているのか分かりづらくなっています。

ちょうど今日のドメイン居酒屋用に図を作ったので、これを元にSimpleModelerの最新状況について説明します。




DSL

SimpleModelerの最新状況では以下のDSLの仕様を想定しています。

  • SmartDox DSL
  • CSV DSL
  • Mindmap DSL (XMind)
  • Excel DSL (予定)

基本となるDSLはSmartDox DSLです。

CSV DSL, Mindmap DSL, Excel DSLはモデルを部分的に記述するのに用います。

Scala DSL

SimpleModelerは元々Scala DSLをメインのDSLにしていたのですが、以下の理由によりSmartDox DSLに置き換えることにしました。

  • 日本語による仕様記述を書きにくい。
  • コンパイルが必要になるのは運用的にマイナス。

日本語による仕様記述はJavaに対してのScalaのアドバンテージでしたが、プレインテキスト文書であるSmartDoxをベースにしたSmartDox DSLの方がより簡単に書くことができます。

開発サイクル

現状では、SmartDox DSL生成、DSLマージ機能、Excel DSLが予定となっており、SmartDox DSL、CSV DSL、Mindmap DSLのいずれかからモデルを読み込んで成果物の生成を行います。

最終的には予定機能を実現後に以下の開発サイクルを想定しています。

  1. 基本モデルをSmartDox DSLで記述
  2. 追加部分をMindmap DSLなどで記述。ブレインストーミングで作成したモデルなど。
  3. SmartDox DSLとMindmap DSLなどをマージしたモデルをSmartDox DSLへ反映。
  4. Smartdox DSLを編集して最終モデルを作成。
  5. 各種成果物を生成。

SimpleModel

各種DSLで記述されたモデルはマージされて、SimpleModeler内部でSimpleModelというモデルとして表現されます。このSimpleModelから各種成果物を生成します。

コード生成

現在プログラム生成の基本プラットフォームと考えているのは以下の2つです。

  • Java EE
  • Play2 + Ext-JS
Java EE

Javaコードとしては、基本JavaにプラスしてJava EE Webプラットフォーム系の以下のアノテーションを付加したコードを生成します。

  • JPA
  • Dependency Injection
  • JAX-RS
Play2 + Ext-JS

Webアプリケーションとしては、個人的な好みもありPlay2とExt-JSの組み合わせによるRIAを基本ターゲットにしています。

クライアント側のExt-JSとサーバー側のScalaコードを生成します。

g3とg4

また、クラウドアプリケーションでのアプリケーション開発技法やフレームワーク技術を調査するために試作した2つのフレームワークg3(Cloud Serivce Framework)とg4(Android Framework)向けのコード生成も行います。

仕様書とクラス図

SimpleModelで記述したモデルから仕様書とクラス図を生成することができます。モデルに直接記述した情報だけでなく、誰が誰から参照されているといったバックリファレンス情報、直目しているクラスを中心にしたクラス図の生成なども行います。

Asakusa Framework

SimpleModelerでAsakusa FrameworkのDSLを生成することを計画中です。

SmartDox DSL用のデータフローの記述方式を思いついたので、この記述からAsakusa DSLを生成します。

現状の現状

現時点では、メタモデルを色々といじったこともあり、一時的に動かなくなっている機能があります。現在デバッグ中なので、近いうちに安定版をリリースしたいと思います。

2012年10月25日木曜日

Scala Tips / Scala 2.10味見(17) - NonFatal

Scala 2.10で導入される機能の中で、地味だけどちょっと注目しておきたいものとしてscala.util.control.NonFatalがあります。

Scalaプログラミングでも例外のハンドリングは引き続き重要項目です。scala.util.control.NonFatalは例外の分類に関するユーティリティ・オブジェクトです。

Javaの例外は大きく以下の4つに分類できます。

java.lang.Error
致命的なエラー
java.lang.RuntimeException
非検査例外
Runtime以外のException
検査例外
それ以外のThrowable
特殊用途

java.lang.Error(以下Error)とjava.lang.RuntimeException(以下RuntimeException)はどちらもthrows句で宣言しないでも発生する可能性がある例外です。その心は、システム側の致命的なエラー(Error)またはアプリケーションのバグ(RuntimeException)なので例外をキャッチしてもリカバリしようがないので、精密な例外ハンドリングが必要な場合以外は無視してよい、ということでしょう。

ご存知の通り、Scalaでは検査例外の機能がなくなってしまったので、java.lang.RuntimeExceptionとそれ以外のExeptionの違いがプログラミングに反映されなくなってしまう傾向は出てくるでしょう。

逆に、例外の使い方に対する制約もなくなったので、アプリケーションのニーズに従って例外を分類して使用するようにしておくと良いと思います。

例外に対する処理

例外に対する処理は概ね以下のものになります。

  1. エラー処理は放棄して上位に例外をそのまま上げる。
  2. 利用者にエラーを通知したりデフォルト値を返すなどして普通の処理に戻す。
  3. 例外を握りつぶす。(ログは取る事が多い)
  4. リトライする。

JavaではErrorとRuntimeExceptionは「1. エラー処理は放棄して上位に例外をそのまま上げる。」が基本操作となる例外と想定していると考えられます。

ここで問題となるのは、JavaではError扱いの例外であっても必ずしもシステム側の致命的な障害とは限らないということです。たとえば、StackOverFlowErrorは再帰呼び出しでスタックがあふれた時に発生するケースが多いですが、これは基本的にはプログラムのバグと考えてよいものです。

このため、このようなケースでは「1. エラー処理は放棄して上位に例外をそのまま上げる。」は適切ではなく、「2. 利用者にエラーを通知したりデフォルト値を返すなどして普通の処理に戻す。」や「3. 例外を握りつぶす。(ログは取る事が多い)」といった処理を選択する必要があります。

この切り分けの機能を提供しているのがNonFatalです。

NonFatalでは以下の例外を致命的なエラーと判定します。

  • VirtualMachineError(StackOverFlowError以外)
  • ThreadDeath
  • InterruptedException
  • LinkageError
  • ControlThrowable
  • NotImplementedError

これ以外の例外はErrorであっても、致命的例外とはみなさないということになります。

ControlThrowable

致命的エラーか否かという観点とは別に、制御フローの実装技術として例外を使うケースがあります。Scalaの基本ライブラリではscala.util.control.ControlThrowableがそれに当たります。この例外に対しては形の上で致命的エラーと判定します。こうすることで、アプリケーションがControlThrowableを誤ってキャッチしてしまうことを防ぎます。

使い方

NonFatalのScaladocでは、以下のような使い方を紹介しています。

try {
     // dangerous stuff
   } catch {
     case NonFatal(e) => log.error(e, "Something not that bad.")
    // or
     case e if NonFatal(e) => log.error(e, "Something not that bad.")
   }

つまり、NonFatalと判定される例外はキャッチしてしかるべきエラー処理をしますが、それ以外の例外はアプリケーションでのエラー処理は諦めるという使い方ですね。

Try

NonFatalが重要な理由の一つとして、scala.util.Tryがキャッチすべき例外の判定にNonFatalを使用していることがあります。

Tryが必ずしもすべての例外をキャッチしてモナドの文脈に乗せてくるわけではないという点は注意しておくべきです。致命的と判断された例外はそのまま例外のメカニズムに乗って上位に渡っていきます。

もう一点、TryがScala 2.10以降のエラー処理の重要なキーパーツになると思いますが、そのTryが致命的例外とそうでない例外の切り分けに使用しているロジックは、アプリケーション側でもできるだけ合わせておいたほうが得策です。そういう意味で、特別な考察が必要なケース以外はNonFatalを使うようにするという方針でプログラミングに臨むとよいでしょう。

諸元

  • Scala 2.10.0-RC1

2012年10月24日水曜日

トレイト・モデリング

先日行われたScala基礎勉強会はどの発表もとても刺激的でしたが、特に僕が触発されたのは:

  • Deprecating the Observer Pattern
  • JavaScript as an Embedded DSL

の2つです。

どちらも関数型言語のテクニックというより、トレイトの活用テクニックで、つまりオブジェクト指向プログラミングの最新技法ということができるでしょう。論文を見ていただければ分かりますが、その効果は驚異的です。

以前OFP(Object-Functional Programming)の三種の神器としてトレイトを取り上げましたが、恐らくScalaプログラミングという文脈では、その威力はモナドや型クラス以上でしょう。

つまり、Scalaをオブジェクトと関数のハイブリッドとしてのみの認識で見ているとScalaの実力を見誤ってしまいます。(これだけでもすごいのですが)

トレイトの導入によって、新次元のオブジェクト指向プログラミングができるという観点からの評価も必要でしょう。

モデリングにトレイトを導入

Scalaにおけるトレイトの威力は日々のScalaプログラミングで感じているわけですが、ちょっと思いついてSimpleModelerに組み込んでみたところ、モデリングの用途にもかなりいい感じになったということは「MindmapModelingと集合知(10) - トレイト」で説明しました。

この点をもう少し補足しておきましょう。



図の左上にあるのが、トレイトを用いたモデルです。A, B, Cがそれぞれトレイトで、クラスAがこの3つのトレイトをミックスインしています。

右上にあるのが、データベースのテーブルです。トレイトA, B, CとXの項目をすべて合体した表Xが定義されます。

下にあるのが、JavaやScalaといったプログラムです。データベースの表の各行は1つのオブジェクトとしてインスタンス化されます。

データベースの表の設計から開発を開始すると、表を構成する要素であるA, B, Cは他の表との共通部分であっても、共通部分ということはモデル上から陽には分かりません。設計者だけが知っている暗黙の情報ということになってしまいがちです。

このため表のメタデータから自動生成したJavaクラスではこういった共通情報をうまく抽出することはできません。このため、共通情報を操作するコンポーネントを用意するようなアプローチの適用が難しくなります。(逆に、動的言語だとこのあたりがスムーズにいきます。)

一方、モデルでトレイトを導入した場合は事情が異なります。上の例にあるようにデータベースの表ではひとつの大きな表になり、読み込んだオブジェクトもひとつの大きなオブジェクトになりますが、トレイトA, B, Cに対応する部分は、インタフェース(Javaの場合)やトレイト(Scalaの場合)としてプログラム化するので型安全にコンポーネントを適用することができます。

データベース中心設計への影響

現在は、データベースのER図と画面設計の二本立てで設計を進め、それぞれの実現であるデータベースとWeb UIを構築する開発方法が広く使われています。

このアプローチは余分なモデリングを必要としないのでプログラミング中心の開発とも相性がよくなっています。

従来のオブジェクト指向モデリングでは、オブジェクト・モデルとER図(あるいはRDBMSのデータベーススキーマ)のセマンティクス・ギャップは継承周りにとどまっています。これも大きなギャップではあるのですが、継承をできるだけ避けたり、Single Table Inheritance的なテクニックを使うことにより、オブジェクト指向モデリングをせず、直接ER図による設計から入っても十分に開発を行うことができたわけです。

別の言い方をすると、オブジェクト・モデルとER図がほとんど同じ形になるケースでは、オブジェクト指向モデリングした結果をデータベース・スキーマに変換する手間を掛けるより、直接ER図でモデリングしたほうが効率がよくなります。

そこでトレイトです。

モデリングにトレイトが入ってくると、単なる継承とは別次元のセマンティクス・ギャップが発生します。こうなってくると、直接ER図でモデリングするより、オブジェクト指向モデリングの結果をER図に変換するほうがより良い結果を得ることができるはずです。

もちろん、モデリングにトレイトを導入しても手作業でデータベース・スキーマに変換していたのでは効果は限定的です。しかし、SimpleModelerのようなモデルコンパイラによって、このあたりの変換処理が自動化されれば、トレイト導入の効果を十二分に享受することができます。

というようなことを、SimpleModelerがトレイトを処理した結果のクラス図を眺めて感じたしだいです。こういった新しいモデリング手法が浸透するまでには長い時間が必要になるので、今すぐER図中心、データモデリング中心の開発アプローチが下火になることはないでしょうが、長い目で考えると大きく動いていくところではないかと思います。

2012年10月23日火曜日

Literate modeling

10月23日の昼にとあるプライベートな集まりで、夜に名古屋Geek BarでSimpleModelerを紹介する機会がありました。参加された皆さん、どうもありがとうございました。


今回は新機能であるSmartDox DSLを中心に紹介しました。

ユビキタス言語

SimpleModelerの主張点の一つは、ユビキタス言語を軸にビジネス・モデリングから分析、設計、そしてコード自動生成を束ねていく点にあります。

この目的に、より近づくために開発したのがSmartDox DSLです。

SmartDoxは、Emacs org-modeをベースにした文書処理システムです。これをオブジェクト指向モデルの記述言語に採用したものがSmartDox DSLです。

SmartDoxは普通の文章を記述するためのフォーマットですが、この中に、一定のコンベンションに沿った文章を書くことで、この文章の中からオブジェクト・モデルを抽出し、モデルの可視化やコード生成を行うものです。このようにして抽出されたオブジェクト・モデルは、自然言語によって記述された文書との整合性を持つはずです。この部分が日本語、モデル、プログラムの共通部分、すなわちユビキタス言語となります。

Literate modeling

ユビキタス言語を軸に説明する方針をとっていたのですが、説明をしながら思いついたのはLiterate modelingの観点をもう少し強調した方がよいかもという点です。

Literate modelingはLiterate programmingのモデリング版で『Enterprise Patterns and MDA』で提案されているものです。

UMLによるビジュアル・プログラミングだけでは情報不足という認識から、UMLと同時にビジネス文脈文書(Business context document)を作成し、この2つをあわせてモデリングの成果物とします。

『Enterprise Patterns and MDA』版Literate modelingでは、UMLが主でこれを自然言語文書で補完しますが、SmartDox DSLのLiterate modelingではこれを一歩進めて自然言語文書+コンベンションで実現します。

スライドにも柔らかいモデル、固いモデルという表現が出てきますが、これをもう少し精密化して、Literate modelingの文脈で説明していくのがよいのではないかと考えたわけです。

SmartDox DSLで書かれた自然言語文書(+コンベンション)の文書から抽出されるものは以下の3つに分類できます。

非モデル
モデル化できないマテリアル。自然言語、図など。
柔らかいモデル
プログラムに落とすことはできないが、モデルとしてデータ化することができる。
固いモデル
プログラムを自動生成できる。

SmartDox DSLでは非モデル、柔らかいモデル、固いモデルを一つの文書に束ねて記述します。このため、プログラムと直接対応を持たない非モデル、柔らかいモデルがプログラムと生き別れになってしまい散逸してしまうことを防ぐことができます。

柔らかいモデルは、要求仕様といったビジネス側の要件をオブジェクト指向モデルの枠組みにそって記述したものです。この柔らかいモデルが固いモデルを束ねる形になりますが、このメカニズムにより固いモデル経由で、仕様記述、ビジネス・モデリング記述とプログラムの関係を明示することができます。

また、非モデルも柔らかいモデルや固いモデルの説明として紐付けられ、モデルの中に束ねられます。

説明中に思いついたアイデアをより具体化してみました。Literate modelingはSimpleModelerの特徴をわかりやすく表現できるようです。SimpleModelerのモデル記述方法に関する説明は「ユビキタス言語」と「Literate modeling」の2つを軸に洗練させていきたいと思います。