2012年2月2日木曜日

Scala Tips / Option (6)

Optionから値を取り出すイディオムです。

以下の表が示す演算について考えています。

条件結果
Option[A]に有効な値が入っているSome[B]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone

withFilterバージョン、collectバージョンに続いて、flatMapバージョンです。

引き続きIntは0以上のものが有効という条件付きのOption[Int]からOption[String]へ変換を例に考えてみます。

(分類の基準)

Java風

Option(4)と同じです。

Scala風

Option(4)と同じです。

Scala

Option[Int]のflatMapメソッドは、Option[Int]の格納する値であるIntを引数に取り、この場合はOption[String]を返す関数を実行します。Option(3)で取り上げた、mapメソッドの場合はIntを引数に取りStringを返す関数を実行し、その結果をmapメソッド側でOptionに詰めなおしますが、flatMapの場合は、StringをOptionに詰め直す作業もアプリケーション側の関数で行います。

def f(a: Option[Int]): Option[String] = {
  a.flatMap { b =>
    if (b >= 0) Some(b.toString)
    else None
  }
}

Scalaz

Scalazの場合もflatMapメソッドを使いますが、flatMapの中のロジックをより簡潔に記述することができます。

def f(a: Option[Int]): Option[String] = {
  a.flatMap(b => (b >= 0).option(b.toString))
}

Scalazでは、Booleanにoptionメソッドを拡張しています。optionメソッドでは、Booleanが真の場合、指定した関数が実行され、その結果をSomeに詰めたものが返されます。一方、偽の場合はNoneが返されます。

(b >= 0).option(b.toString)は、bの値が0以上の場合にbをStringにしたものをSome[String]に詰めて返し、そうでない場合はNoneを返します。

Scalazでは、このように式を簡潔に記述できる便利なメソッドが多数追加されています。

ノート

今回の用途では、Optionのfilter/withFilterメソッドやcollectメソッドで十分に要件を満たせるので、イディオムとしてはこの2つだけもよかったのですが、プリケーションの意志で成功の文脈を失敗の文脈に切り替えるということを、もっと直接的な形で行う手法をマスターしておかないと応用が効かないので、flatMapメソッドも取り上げました。

以下の表の演算において:

条件結果
Option[A]に有効な値が入っているSome[B]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone

「Option[A]に無効な値が入っている」部分が、アプリケーションの意志で成功の文脈を失敗の文脈に切り替えるところになります。

filter/withFilterメソッドやcollectメソッドは自動的にこの切り替えを行ってくれるわけですが、それぞれのメソッドの機能ははっきり決まっていて、提供された機能以外の用途に使うのは得策ではありません。

そういった汎用目的で成功の文脈を失敗の文脈に切り替える処理を行うのがflatMapメソッドです。

mapメソッドを使うと、成功の文脈における演算を記述することができましたが、成功の文脈を失敗の文脈に切り替えることはできませんでした。flatMapメソッドは、この切り替えを記述するためのメソッドというわけです。

モナドは計算文脈をカプセル化して扱う技術と考えることができます。(モナドの一種であるOptionは成功/失敗の文脈をカプセル化していました。)この計算文脈の操作に色々な手法が存在するわけですが、その中軸となるのがflatMapメソッドです。

flatMapメソッドを使いこなせるようになるとScalaプログラミングの幅がぐんと広がります。

filter, collect, flatMapの使い分け

前回はfilterメソッドとcollectメソッドの使い分けについて説明しましたが、これにflatMapが加わりました。

filterメソッドやcollectメソッドは、使い方の形が決まっているので、これにぴったりハマるケースはfilterメソッドやcollectメソッドを使えばよいでしょう。

あまりぴったりはまらないケース、判定ロジックと生成ロジックが入り乱れているような場合に、flatMapメソッドを使うことになります。

諸元

  • Scala 2.9.1
  • Scalaz 6.0.3

2012年2月1日水曜日

Scala Tips / Option (5)

Optionから値を取り出すイディオムです。

以下の表が示す演算について考えています。

条件結果
Option[A]に有効な値が入っているSome[B]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone

withFilterバージョンに続いて、collectバージョンです。

前回はwithFilterメソッドとmapメソッドを組合せてこの表の演算を実現しましたが、これを一発で行うメソッドがあります。これがcollectメソッドです。

引き続きIntは0以上のものが有効という条件付きのOption[Int]からOption[String]へ変換を例に考えてみます。

(分類の基準)

Java風

Option(4)と同じです。

Scala風

Option(4)と同じです。

Scala

Option#collectメソッドにケースシーケンスを用いて部分関数(PartialFunction)を指定します。

指定した部分関数は、「指定されたInt値が0以上の場合には値をStringに変換する」というものです。指定されたInt値が0以上でない場合には、この部分関数は適用されません。

以下のプログラムでは、Option[Int]がSome[Int]の場合、部分関数が適用可能か(つまりInt値が0以上か)を検査し、適用可能の場合に部分関数を評価することでInt値をStringに変換します。collectメソッドはさらにStringをSome[String]に詰めなおして返します。また、Option[Int]がNoneの場合、あるいはSome[Int]だけど部分関数が適用可能でなかった場合はNoneを返します。

def f(a: Option[Int]): Option[String] = {
  a collect {
    case b if b >= 0 => b.toString
  }
}

Scalaz

Scalaz流のエレガントな書き方はないと思います。

ノート

以下の表が示す演算について考えています。

条件結果
Option[A]に有効な値が入っているSome[B]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone

withFilterメソッドとmapメソッドを組合せて実現していた処理をcollectメソッド一発で実現できました。

collectメソッドは非常に強力ですが、その力の源となっているのがPartialFunctionという関数オブジェクトです。部分関数と呼ぶこともできます。collectメソッドはPartialFunctionを引数に取ります。

関数の引数に指定された型の一部の値のみを評価できる関数が部分関数です。評価できない値に対しては関数は未定義となります。

ケースシーケンスや部分関数、PartialFunctionという用語は見慣れないかもしれませんが、プログラムをみれば、やろうとしていることは一目瞭然だと思います。まずは、match式の後ろ半分case句の集りがケースシーケンスで、ケースシーケンスはPartialFunctionという関数オブジェクトに落とし込まれる、と覚えておいてください。(使い方に慣れてきたら文法書を確認して正確な定義を理解しておくとよいでしょう。)

PartialFunctionをScalaプログラムのリテラルとして記述する方法がケースシーケンスです。ケースシーケンスはmatch式からキーワードmatchを除いた後ろ半分の形です。今回のプログラムの以下の部分ですね。(アスタリスクは無視)

******** {
    case b if b >= 0 => b.toString
  }

処理の意味はmatch式のcase句と同様です。指定されたInt値を変数bにバインドし、このbの値が0以上の場合は、Int値をStringに変換します。0以上の値でない場合は、この部分関数は未定義となります。部分関数が未定義の場合、Option#collectメソッドはNoneを返します。

PartialFunctionの主要なメソッドは以下の2つです。

メソッド機能
isDefinedAt指定された値が関数の引数として有効化を判定
apply関数の評価

通常の関数オブジェクト(Function1など)はapplyメソッドは持っていますが、isDefinedAtは持っていません。

isDefinedAtメソッドは指定された値が関数に取って有効かどうかを判定するメソッドです。collectメソッドは、このisDefinedAtメソッドを使用して、現在扱っている値に対して部分関数が未定義がどうかを判定しているわけです。

前述のケースシーケンスの場合、case句中の「if b >= 0」がisDefinedAtメソッドの実装に、「b.toString」がapplyメソッドの実装になるわけです。ケースシーケンスは、こういったPartialFunctionオブジェクト定義の文法糖衣というわけですね。

PartialFunctionはScalaプログラミングの様々な場所に現れ、非常に重要な役割を担います。これからも色々なイディオムに登場すると思います。

filter, collectの使い分け

今回の用途では、filterメソッドとcollectメソッドはどちらも似たような効果を得ることができます。好みの方を使うとよいでしょう。

filterメソッドの方が機能が絞り込まれている分、プログラムがやりたいことの意図ははっきりすると思います。

ボク自身のプログラミングでは、多分collectメソッドの方を使うと思います。ちょっとだけ性能が速そうなので。

filterメソッドとcollectメソッドで明らかにcollectメソッドが有用なケースもあるのですが、これは別の機会に取り上げたいと思います。

諸元

  • Scala 2.9.1
  • Scalaz 6.0.3

2012年1月31日火曜日

Scala Tips / Option (4)

Optionから値を取り出すイディオムです。
前回は、Optionに値が格納されているケースといないケースの両方に対応するイディオムでした。今回は、これを拡張してOptionに格納されている値が行おうとしている処理に対して無効だったケースを追加します。
前回の演算は以下の表の示す演算でしたが、これを拡張して:
条件結果
Option[A]がSome[A]Some[B]
Option[A]がNoneNone
以下の表が示す演算にします。
条件結果
Option[A]に有効な値が入っているSome[B]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone
「Option[A]に無効な値が入っている」のケースが加わっています。
以下では、引き続きOption[Int]からOption[String]への変換を例に考えてみます。ただし、Intは0以上のものが有効という条件を追加します。
Optionに入っているIntが0以上の場合、Some[String]が処理結果となります。一方、0未満の場合は無効となりNoneが処理結果となります。
(分類の基準)

Java風

if式でOption#isDefinedを使ってOptionが有効であることを確認後、さらにif式で値が0以上であることを判定します。このプログラムにあるように a.get を二度行なうか、a.get の値を変数に覚えておかなくてはいけません。
def f(a: Option[Int]): Option[String] = {
  if (a.isDefined) {
    if (a.get >= 0) Some(a.get.toString)
    else None
  } else {
    None
  }
}

Scala風

match式を使うと以下のようになります。a.get の二回判定(または a.get の値を変数に記憶)が必要なくなるのでその点はすっきりしています。とはいえ「Noneの場合はNone」の処理を記述するのはScala的には悔しい感じ。
def f(a: Option[Int]): Option[String] = {
  a match {
    case Some(b) => if (b >= 0) Some(b.toString) else None
    case None => None
  }
}

Scala

Scala的なコーディングでは、withFilterメソッドを使って、「0以上」という条件に合わない値の場合に、Some[A]からNoneへの切替えを行います。
def f(a: Option[Int]): Option[String] = {
  a.withFilter(_ >= 0).map(_.toString)
}

Scalaz

Scalaz流のエレガントな書き方はないと思います。

ノート

前回は以下の表の演算を行いましたが、今回はこれを拡張して:
条件結果
Option[A]がSomeSome[B]
Option[A]がNoneNone
以下の表の演算を行いました。
条件結果
Option[A]に有効な値が入っているSome[B]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone
「Option[A]に無効な値が入っている」のケースが加わっていますが、この追加が何を意味しているのかというと、成功の文脈だったものをアプリケーションの意思で失敗の文脈に変えることができるということです。
mapメソッドを使うと、成功の文脈における演算を記述することができましたが、成功の文脈を失敗の文脈に切り替えることはできませんでした。
withFilterメソッドは、指定した条件が真の場合はSome[A]を返し、偽の場合はNoneを返します。また、Noneを受け取った場合はそのままNoneを返します。つまりwithFilterメソッドを使うことで、成功の文脈を失敗の文脈に切り替えることができるわけです。
withFilterメソッドは以下の表の演算を行います。
条件結果
Option[A]に有効な値が入っているSome[A]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone
また、mapメソッドは以下の表の演算を行います。
条件結果
Option[A]がSome[A]Some[B]
Option[A]がNoneNone
この2つの表を連結すると以下の表になります。
条件結果
Option[A]に有効な値が入っているSome[B]
Option[A]に無効な値が入っているNone
Option[A]がNoneNone
以上のようにwithFilterメソッドとmapメソッド連結することで、全体として期待した結果が得られます。
withFilterとfilter
withFilterメソッドとほぼ同じ機能を持つfilterメソッドを使っても同じ結果が得られます。
filterメソッドとwithFilterメソッドは、指定された条件でOptionの値をフィルタリングして、条件に合わない場合は成功の文脈(Some[A])から失敗の文脈(None)に切り替える点は同じです。
異なるのは、filterメソッドはOption[A]を返すのに対して、withFilterメソッドはWithFilterというオブジェクトを返す点です。WithFilterオブジェクトは、mapメソッドなどつないでいく場合に利用するオブジェクトです。
filterメソッドはメソッドチェインの最後に位置する場合に用いるのに対して、withFilterメソッドはメソッドチェインの内部にあり後続に別のメソッドがつながれる場合に使用します。メソッドチェインの最後に位置する場合は、Option[A]のオブジェクトを生成してこれを返すことになりますが、その後にすぐにmapメソッドなどがつながれる場合は、このOption[A]の生成が無駄になります。このような無駄を省くために用意されているのがwithFilterメソッドです。
filterメソッドとwithFilterメソッドの使い分けがよく分からない場合は、filterメソッドを使っておくと安全です。ただし、若干ですが性能的に不利になります。
filterメソッドとwithFilterメソッドには以上のような機能差があるため、Optionの値をフィルタした後にmapメソッドなどをつないでいく場合は、filterメソッドより高速に動作するwithFilterメソッドを使用するのがテクニックになっています。このためイディオムではwithFilterメソッドを採用しました。
collectメソッドとflatMapメソッド
Optionでは、filter/withFilterと同じような使い方ができるメソッドとしてcollect、filterNotが提供されています。
filterNotメソッドはfilterメソッドと条件式が反対に作用するものです。
collectメソッドは部分関数を用いて、Optionの値を変換しながら、成功の文脈と失敗の文脈の切り替えもできるという優れもののメソッドです。
また、今回のイディオムはより汎用性の高いメソッドであるflatMapを用いて実現することもできます。
collectメソッドとflatMapメソッドを使ったイディオムについて、続けて取り上げていきます。

諸元

  • Scala 2.9.1
  • Scalaz 6.0.3

2012年1月30日月曜日

SmartDox 0.2.1 / クラス図の生成埋込み

SmartDox 0.2.1をリリースしました。
SimpleModelerを使って、CSVからクラス図の画像生成と埋込みができるようになりました。

機能

SmartDox 0.2.1では以下のオプションを提供しています。
オプション機能
-html5HTML5生成(試験的)
-html4HTML4生成
-html3HTML3生成
-plainプレインテキスト生成
-pdfPDF生成
-latexLaTeX生成
-bloggerBlogger用のHTML生成
基本的にSmartDox 0.2と同じです。
CSVで記述した情報からクラス図の画像を生成して、文書内に埋め込む機能をサポートしました。

サンプル

SmartDox 0.2.1では以下のようなorg-mode文書が扱えます。
#+TITLE: simplemodeler

* SimpleModelerによるクラス図記述

SimpleModelerを使ってCSVでクラス図を
書くことができます。

#+begin_sm_csv images/sm_csv_simplemodel.png
#actor
顧客
個人顧客,,,,,顧客
法人顧客,,,,,顧客
#resource
商品,商品名,,商品区分(第1類;第2類;第3類)
#event
購入する,,顧客;商品
#+end_sm_csv
#+begin_sm_csv images/sm_csv_simplemodel.png で始まるところがCSV言語で記述したクラス図情報です。この記述から自動的にクラス図の画像(png)を生成して文書に埋め込みます。
この文章中にあるCSVからSmartDoxが(SimpleModeler経由で)生成するクラス図画像は以下のものです。

クラス図情報を記述するCSVの文法は次回に説明します。

PDF

クラス図画像の生成はHTMLやプレインテキストの生成時にも行いますが、ここではPDFを例にして生成されたクラス図画像がどのように文書中に埋め込まれるのかをみてみましょう。
PDFの生成は以下のようにして行います。
$ dox -plain sample.dox
2ページのPDFが生成されます。1ページ目はタイトルと目次ですが、2ページ目に以下のように生成されたクラス図が本文に埋め込まれています。

インストール

プログラムの配布は、Scalaで最近注目されているconscriptを使っています。conscriptのインストール方法は以下のページに詳しいです。
Linux, Macであれば、以下のようにすればインストール完了です。
$ curl https://raw.github.com/n8han/conscript/master/setup.sh | sh
conscriptをインストールした後、以下のようにしてSmartDoxをインストールします。
$ cs asami/dox
以下の2つのコマンドがインストールされます。
dox
SmartDoxコマンド
sdoc
SmartDocコマンド(互換用)
依存プロダクト
SmartDoxでは、以下のプロダクトに依存しています。
プロダクト使用する機能
LaTeXPDF生成
Graphviz画像生成
Ditaa画像生成
プロダクトに依存する機能を使わない場合は必要ありません。
LaTeX
platexコマンドとdvipdfmxコマンドが実行可能になっていれば基本的にはOKです。
Mac OS上でmacportsを使ってインストールしたLaTeXで動作確認しています。他の環境の場合、スタイルファイルなどがない可能性があります。
Graphviz
dotコマンドが実行可能になっていればOKです。
Mac OS上でmacportsを使ってインストールしたGraphvizで動作確認しています。
Ditaa
ditaaコマンドが実行可能になっているか、optlocalsharejavaditaa09.jarのJarファイルが存在していればOKです。
Mac OS上でmacportsを使ってDitaaをインストールすると、optlocalsharejavaditaa09.jarに配置されます。このditaa09.jarを決め打ちで使用しています。(いずれパラメタで指定可能にする予定です。)
それ以外の環境では、シェルスクリプトなどでditaaコマンド(インストールされているJarファイルを呼び出す)を作成してください。

使い方

まだマニュアルがないので、文書フォーマットは org-modeを参考にしてください。あまり難しい文法を使わなければ大体大丈夫だと思います。
org-mode形式で作成した文書から以下のようにしてHTMLやPDFに変換してください。
$ dox -html4 mydoc.dox
$ dox -pdf mydoc.dox

2012年1月27日金曜日

Scala Tips / Option (3)

Optionから値を取り出すイディオムです。

Optionの値を取り出すのは、Option#getメソッドを使うかパターンマッチングでSome(x)とするのが普通ですが、イディオムとして整理する場合は、適材適所という切り口が重要になるので、もう少し大きな粒度のシーケンスでまとめておきたいところです。

Optionから値を取り出す処理は以下の2つのコーディングパターンのいずれかの構成要素として使われることが多いでしょう。それぞれのケースについて考えていきます。

  • Option[A]からOption[B]に変換
  • Option[A]からBに変換

まず、「Option[A]からOption[B]に変換」のイディオムです。Option[A]からOption[B]に変換するケースで注意が必要なのは、Option[A]がSome[A]ではなくNoneだったケースです。この場合は、AからBへの変換処理は行わずNoneにしなければなりません。これを表にまとめると以下のものになります。

条件結果
Option[A]がSome[A]Some[B]
Option[A]がNoneNone

Optionから値を取るイディオムでは、Noneのケースも包含した形にしておく必要があるというわけです。

以下では、Option[Int]からOption[String]への変換を例に考えてみます。

(分類の基準)

Java風

if式でOption#isDefinedを使って値の有無を判定します。Option#isDefinedとOption#getが泣き別れになるので、あまりよい感触ではありません。

def f(a: Option[Int]): Option[String] = {
  if (a.isDefined) Some(a.get.toString)
  else None
}

Scala風

match式を使うと以下のようになります。こちらの方が綺麗ですね。

def f(a: Option[Int]): Option[String] = {
  a match {
    case Some(b) => Some(b.toString)
    case None => None
  }
}

Scala

Optionの処理にmapメソッドを使うのがScala的なコーディング。mapメソッド内に処理を記述します。(処理を行う関数を引数に指定します。)Noneの場合にはNoneになる、という処理はmapメソッド内で自動的に行ってくれるのがミソです。

def f(a: Option[Int]): Option[String] = {
  a.map(_.toString)
}

Scalaz

Scalaz流のエレガントな書き方はないと思います。

ノート

Optionの心は「成功/失敗の計算文脈」にあります。成功または失敗の状況を文脈として持ちまわるモナドという意味です。ボクが現時点で理解しているOptionの意味ですが、この言葉にピンと来ない場合もあまり気にする必要はありません。

実用的には、この抽象的な概念が具体的にどう役に立つのかという点が重要です。具体的に役に立つコーディングパターン、すなわちイディオムを洗いだしてマスターしておけばよいでしょう。

「成功/失敗の計算文脈」が具体的に何を指すのかという点について、前述した以下のmapメソッドを例に考えてみましょう。

def f(a: Option[Int]): Option[String] = {
  a.map(_.toString)
}

Option#mapメソッドは関数を引数に取りますが、成功の文脈(すなわちSome[Int]の場合)では関数を実行した結果(String)を再度Someに詰め直したもの(Some[String])を返却値とします。Someに詰め直すということは値を更新しつつ、成功の文脈を引き継ぐということです。

一方、失敗の文脈(すなわちNoneの場合)は何もせず自動的にNoneを返却値とします。つまり、失敗の文脈の処理についてはプログラマは直接指定する必要はなく、Option側で自動的に行ってくれるということです。失敗の文脈が自動的に引き継がれる点は、Java風(if式を使う場合)やScala風(match式を使う場合)と比べてみると違いが明確です。

プログラマの関心が低い方の文脈である失敗の文脈が、Optionが内包しているロジックにより自動的に引き継がれるのがOptionの旨みで、Optionの実現技術であるモナドの威力です。

2012年1月26日木曜日

「ソーシャルメディアマーケティングの基本戦略」のマインドマップとクラス図

1月21日(土)に横浜モデリング勉強会を行いました。また、会場には(株)アットウェア様の会議室をお借りしました。参加された皆さん、アットウェア様、どうもありがとうございました。懇親会では、勉強会でモデル駆動開発のハンズオンみたいなこともどうか、という意見もありました。そちらの方向への展開も考えてみたいと思います。
この勉強会で、浅海が作成したモデルを紹介します。モデルはMindmapModelingの手法で作成しました。(勉強会で使用したチュートリアル)
モデリングの対象は、IT media エンタープライズ ビジネスイノベーション誌に掲載された小川浩氏の記事「ソーシャルメディアマーケティングの基本戦略 」です。前回、前々回は経済ネタだったのですが、今回はITエンジニアにも親和性の高いソーシャルメディアのマーケッティングをテーマにしてみました。

単語を抜き出す

まず、最初の作業で記事中から単語を抜き出します。単語を抜き出しながら、登場人物、道具、出来事を中心にMindmapModelingの定めた分類に従って仕分けしていきます。
この結果、できた最初のマインドマップが以下のものです。(以下、図をクリックすると拡大します。)

記事の内容がソーシャルメディアマーケッティングの構造についてなので、そのあたりに単語が集中しています。

関係の洗練

次の段階では、抽出した、登場人物、道具、出来事の洗練を行います。用語の名寄せ、用語の種類(generalization)や部品構成(aggregation)を整理していきます。また、この段階で区分(powertype)の抽出を開始します。
以上の作業を行った結果のマインドマップは以下のものです。

SimpleModelerのサービスを使用して作成したクラス図は以下のものになります。

今回は、ソーシャルメディアマーケッティングについての区分を整理を中心に作業しました。自然言語の文章としては意味が通っても、モデルの用語としては曖昧なところが出てくるのでそういった点を補ったり、MECE(Mutually Exclusive and Collectivily Exhausitive、もれなくだぶりなく)を念頭に区分の中身を整理しました。

出来事と物語

次の段階では、対象となる世界の動的な側面を捉える出来事や物語を整理していきます。出来事や物語を洗練していくことによって、出来事や物語における登場人物や道具の役割が明確化され、より登場人物や道具の構造をさらに洗練させることができます。出来事や物語で抽出した役割は、役割(role)としてモデル化して「役割」構造枝に配置し、役割の持つ構造を洗練していきます。

SimpleModelerのサービスを使用して作成したクラス図は以下のものになります。

今回は、よい物語が思い浮かばなかったので、出来事と規則を中心に洗練を進めました。
また、ソーシャルメディアマーケッティングの手法として、記事中で明示的に列挙してあった(1)ペイパーポスト、(2)クチコミを分析、(3)情報発信だけでなく、記事中の他の場所にばらばらに書いてあった(4)コールセンター、(5)対面販売、(6)イベント応募も加えています。
このように著者が形式知として明示したものにも案外抜けがあるものです。これを記事中から拾い集めてきて形式知化し、情報の再構成を行うのがMindmapModelingの目的です。
以上が勉強会での作業です。

仕上げ

最後にドメインモデルの形に仕上げることにしましょう。SimpleModelerを使用して作成したクラス図を見ながら調整していきます。
ついでなのでSimpleModelerの機能を拡張して、より適切なクラス図が表示されるようにしてみました。来週ぐらいにSimpleModelerのサービスを新しいバージョンのSimpleModelerにアップグレードしたいと思います。
以上の作業で作成したマインドマップとクラス図は以下のものになります。


かなりドメインモデルとして整理されました。ドメインモデルがこのレベルの情報を持つようになると、クラス図ベースでモデルの推敲をはじめても大丈夫です。
生成されたクラス図をみてみると、たとえば「市場シェアが二番手、三番手の企業が大域的な市場シェアを奪うためにソーシャルマーケティングを行う」という物語(Business UseCase)を作成し、この物語を軸にモデルの精度をあげると面白いのではないか、とか色々とイメージが湧いてきますね。
初見のモデリングでは、推敲のベースとなる初期モデルを作るのがなかなか大変なので、MindmapModeling→クラス図のアプローチはなかなか有力ではないかと思います。

2012年1月25日水曜日

Scala Tips / null

null値の可能性がある値をnull値ではない普通の値に持ち上げるイディオムです。

(分類の基準)

Java風

if式で変数がnullかどうかを判定します。

def f(a: String, b: String): String = {
  if (a == null) b
  else a
}

実際に使う時は以下のような感じになります。

val c = if (a == null) b else a

Scala風

match式を使う時は以下のようになります。この場合は、Java風の方が簡潔ですね。

def f(a: String, b: String): String = {
  a match {
    case null => b
    case _ => a
  }
}

Scala

OptionとOption#getOrElseを使うと簡潔に記述できます。

def f(a: String, b: String): String = {
  Option(a) getOrElse b
}

Scalaz

Scalazを使うと、さらに簡潔に記述することができます。

def f(a: String, b: String): String = {
  a ?? b
}

以下のような形でメソッドの引数に書きたい場合は、"??"を使った簡潔な記述方法は重宝します。

val a = SomeJavaObject.getStringValue()
println("Value is " + a ?? "unknown")

ノート

プログラムの実行速度という観点では、「Java風」が一番速く、「Scala」はオブジェクト生成とメソッド呼出しが余分に発生するためかなり遅くなります。「Scalaz」は、さらに一段余分にオブジェクト生成とメソッド呼出しが発生するため、さらに低速です。

つまり、Javaの実行速度とScalaの記述力のトレードオフが発生するわけです。実行速度を採るのであればJavaで書けばよいので、Scalaで書く以上、簡潔に記述できることを重視するのがよいでしょう。

まずScalaで書いてみて、プロファイルでホットスポットと判明した処理については、Java風に書き直したり、Java(場合によってはC++)で書いて呼び出すというアプローチになります。