2012年12月21日金曜日

ゆるふわScalaプログラミング・振り返り(7)

11月30日に開催されたBPStudy#63で「Scalaプログラミング・マニアックス」について、「ゆるふわScalaプログラミング」 といった方向性で振り返りをしています。その7はトレイトです。



トレイトの便利さをどのように紹介するのか考えがまとまらないうちに、当日となってしまったのでスライドは今ひとつ焦点の絞れていないものになってしまいました。

そういうこともあり、本ブログでもトレイトを元に以下の記事を書いてみましたが、プログラミング・モデルにおけるトレイトの本質にせまるという感じには至りませんでした。

トレイトには単に多重継承ができる、というだけでない"何か"があるんですよね。

ただ、Tipsとしては有効な内容と思いますので参考にはしていただけると思います。

トレイトについては、「Scala基礎勉強会」で聞いた:

の2つが強烈ですし、またその他定番としては:

もあります。このあたりを盛り込んだ内容にいずれ昇華させたいと考えています。

モデリング

Scalaプログラミングを通じて、トレイトには大いに触発されて、モデリングにも取り入れ、SimpleModelerにも組込みました。

実際に使ってみたところ、トレイトを上流の設計段階からモデル要素として積極的に使っていくアプローチはかなり有効ではないかと感じています。プログラミング言語側のScalaに受け口があるので、モデリングとプログラミングがシームレスにつながる点も重要です。

モデリングとトレイトというと、DCIのようなアプローチも提案されています。

こういった点も含めて、モデリング、プログラミングの双方からトレイトの活用用法、トレイトによって実現できるアーキテクチャ、プログラミング・モデルを探って行きたいと思います。

2012年12月20日木曜日

Scala Tips / トレイトと抽象クラス

クラスの多重継承の問題点は、クラスの継承関係が循環グラフになってしまった場合に、循環してかち合ってしまったクラスの扱いが非常に難しいということです。循環してかち合ってしまったクラスは一つにまとめたほうが良いのか、別管理にしておいた方が良いのか、用途によってどちらもあり得ます。これに付随して、初期化の順番やメソッド呼び出しの解決の順番など、プログラミングを複雑に解決が難しい難問があります。

こういった問題があるため、Javaでは多重継承はサポートしないという選択を行いました。また、「クラスの継承関係が循環グラフになる」という問題を持たないインタフェースという言語機能を用意して、多重継承的な用途を実装可能にしています。インタフェースは実装を持たないため、「実装部」が循環グラフになる問題が起きないわけです。

インタフェースは素晴らしい解決策でしたが、機能が絞りこまれているので用途がかなり限定されます。具体的には、実装の再利用の目的には使用できません。

トレイトは、実装部を持ちながら、継承関係の循環グラフ問題を以下の手法で回避しています。

  • コンストラクタを持たない。
  • 同じトレイトが複数指定されても、トレイト実装の実体は1つになる。

この制約をつけることで、ミックスインが可能になったのは非常に大きな成果ということができます。

一方、プログラミング時に意識しなければならないのは、トレイトにはコンストラクタがなく、コンストラクタを使っての初期化はできないということです。

この制約がどの程度のものなのか、「実装の再利用」とのトレードオフがペイしたものなのか、という点が気になりますね。

トレイトとケースクラス

以下のトレイトPartyと、このトレイトをミックスインしたケースクラスPerson, Companyの構造は頻出のパターンです。

トレイトPartで抽象変数として定義したnameを、ケースクラスPersonおよびCompanyの引数nameで"実装"しているため、このまま普通に動作します。子供(トレイトPersonやCompany)からコンストラクタ経由でパラメタを受け渡す必要はありません。

trait Party {
  val name: String
}

case class Person(name: String) extends Party
case class Company(name: String) extends Party

抽象クラスとケースクラス

これと同様のことを抽象クラスを用いて実装すると以下になります。

abstract class Party(val name: String) {
}

case class Person(n: String) extends Party(n)
case class Company(n: String) extends Party(n)

トレイト版と同じことができているだけですが、記述が少し冗長になります。特にパラメタをコンストラクタに積み直すところはプログラミング時にかなり面倒くさい部分です。

しかも、抽象クラス版では、抽象クラスであるために複数の親クラスを同時に継承、つまり多重継承を行うことができないという弱点があります。トレイト版が多重継承的なことが自在にできることと対照的ですね。

トレイト vs 抽象クラス

トレイトと抽象クラスの例を比べてみましたが、この例だけをみるとトレイトが圧倒的に便利という結論になります。

もちろん、親クラスに対して、コンストラクタで引数を渡して初期化を行うということが有用なケースも多々あるので、この極端な例だけを引き合いに出してトレイトが"圧倒的に便利"というわけにもいきません。

とはいえ、日々のScalaプログラミングの感想では、案外トレイトだけでも大丈夫なケースが多い、というのが実感です。特にイミュータブルオブジェクトの場合、コンストラクタでの初期化で複雑な処理は必要ないのが普通なので、トレイトで十分なケースが多いでしょう。

さらに、関数型プログラミング的な指向が強くなればなるほど、イミュータブルオブジェクトを多用するようになるので、トレイトの優位性が高まります。

そういうわけで、ボクが最近Scalaプログラミングをしながら着目しているのは、"抽象クラスでないといけない用途はなんだろう?"ということです。

トレイトではなく、抽象クラスでないといけない用途が明確になれば、日々のプログラミングで迷いなく抽象クラスとトレイトの選択を行うことができます。最近は、まずトレイトで実装してみて、抽象クラスでないといけないと分かった時に、抽象クラスに変更する、というアプローチをとっています。その結果、あまり抽象クラスに変換するケースはなく、案外トレイトで大丈夫という感触を得ています。

諸元

  • Scala 2.9.2

2012年12月19日水曜日

Scala Tips / トレイトでクラス分割

Scalaプログラミングの便利さを日々体感しているわけですが、その重要な要素の一つがトレイト。トレイトは関数型言語のものではなく、オブジェクト指向プログラミングの技術ですが、そのあるなしはプログラミング・モデルに大きく影響します。

Scalaにおけるクラス設計は、このトレイトの存在だけを意識するとしてもJavaのそれとは別ものということができます。

トレイトには色々な用途がありますが、最近ボクが地味に多用しているのがクラスの分割です。機能数が増えて大きくなったクラスを幾つかのトレイトに分割するだけなのですが、プログラムの見通しがよくなり、また機能間の依存関係も明確になるので、大変重宝しています。

クラス分割の例

以下のクラスがあるとします。変数aに設定した値をメソッドbとメソッドcが使用しています。

class Whole {
  val a = 1
  def b = a + 1
  def c = a + 2
}

このクラスを以下の3つの部品(クラス+トレイト)に分解し、これらの部品を組み立ててクラスを定義します。

Whole
クラス本体。変数aを定義している。
Parta
部品となるトレイト。変数aを使用するメソッドbを定義している。
Partb
部品となるトレイト。変数aを使用するメソッドcを定義している。
class Whole extends Parta with Partb {
  val a = 1
}

trait Parta {
  self: Whole =>

  def b = a + 1
}

trait Partb {
  self: Whole =>

  def c = a + 2
}

ポイントとなるのがPartaのメソッドbとPartbのメソッドcがクラスWholeの変数aに依存していること。普通はこういう場合、クラスWholeとトレイトb, cはインヘリタンスの関係が必要になってきます。

しかし、この場合はトレイトPartaとPartb内のself-type annotationである「self: Whole =>」によって、トレイトがWholeにmixinされることが保証されるためクラスWholeの変数aを使用することができます。

そしてクラスWholeの定義で「class Whole extends Parta with Partb」とすることで、クラスWholeにトレイトPartaとPartbの機能を追加することができます。

この場合、トレイトPartaとトレイトPartbの間には依存関係がないことが明確に分かります。このような形で、機能間の依存関係をできるだけ疎にしていくのが、大きなプログラムを作るときのコツとなります。

実行

実行結果は以下のとおりです。普通に動作しました。

scala> val w = new Whole
w: Whole = Whole@2bf75442

scala> w.a
res0: Int = 1

scala> w.b
res1: Int = 2

scala> w.c
res2: Int = 3

諸元

  • Scala 2.9.2

2012年12月18日火曜日

SimpleModeler 0.4.0

モデルコンパイラSimpleModeler 0.4.0をリリースしました。

マインドマップ(XMind)、SmartDox DSL、CSVからクラス図とJavaプログラムを生成する機能が実用フェーズとなっています。

最近拡張した文法が動作します。

それそろ安定してきたのでバージョン0.4.0とします。

機能

Simplemodeler 0.4.0では以下のオプションを提供しています。

オプション機能状況
projectプロジェクト生成α
importモデル移入α
convertモデル変換試験的
html仕様書生成α
javaJava生成
androidAndroid生成α
diagramクラス図生成
buildプロジェクトビルド試験的
gaejGoogle App Engine Java生成試験的
gaeGoogle App Engine Python生成試験的
gaeoGoogle App Engine Oil生成削除予定
grailsGrails生成試験的
g3g3生成試験的
asakusaAsakusa生成試験的

基本的にはマインドマップ(XMind)、SmartDox DSLとCSVからクラス図とJavaプログラムを生成する処理が実用フェーズになっています。その他の機能はα版または試験的実装の状態です。

インストール

プログラムの配布は、Scala用のプログラム配布ツールconscriptを使っています。

conscriptをインストールした後、以下のようにしてSimpleModelerをインストールします。

$ cs asami/simplemodeler

以下のコマンドがインストールされます。

sm
SimpleModelerコマンド
$ sm -version
Copyright(c) 2008-2012 ASAMI, Tomoharu. All rights reserved.
SimpleModeler Version 0.4.0 (20121217)
graphviz

-diagramオプションでクラス図を生成する場合は、graphvizのインストールが必要です。graphvizのインストール方法は以下を参照してください。

各プラットフォーム向けパッケージ管理ツールでもインストールできるようです。

Mac
http://www.macports.org/
Windows
http://sourceware.org/cygwinports/

使い方

マニュアルはまだありません。以前のバージョン用のものがありますが、機能が色々変わってしまったので一から見直す予定です。

リファレンスマニュアルとユーザーガイドの元ネタをこのブログで随時書いていきます。

CSV

Mind(マインドマップ)、SmartDox DSL、CSVからクラス図を生成することができます。

以下のCSVファイルをsample.csvとして用意します。

#actor,base
顧客
個人顧客,顧客
法人顧客,顧客
#resource,attrs,powers
商品,商品名;定価(long),商品区分(第1類;第2類;第3類)
#event,parts
購入する,顧客;商品

SimpleModelerを以下のように実行します。

$ sm -diagram sample.csv

以下のクラス図の画像が生成されます。

SmartDox

以下のSmartDoxファイルをsample.orgとして用意します。

#+title: Table

SmartDox DSLを使って記述したモデルの
サンプル文書です。

文書でサンプルモデルの定義をします。
本来は文書中の仕様記述の文書は
定義するモデルに対するものになります。

しかし、この文書ではSmartDox DSLの記述例として
SmartDox DSL文法の説明を記述することにします。

* サンプル文書の目的

このサンプル文書は表を中心にしてクラス定義するサンプルです。

登場人物、道具、出来事の各エンティティの種別の下に
顧客、商品、購入といった具象エンティティを節として
定義します。

そして、それらの節の下に属性一覧または特性一覧として
エンティティの属性や関連を記述していきます。

* 登場人物

** 顧客

IDの指定はありませんが、以下のルールで推測しています。

- 陽にID指定がない場合、先頭の属性の属性名が「id」(大文字可)で終わる場合はIDとみなす。

#+caption: 属性一覧
| 名前   | 型     | カラム  | SQL型        |
|--------+--------+---------+--------------|
| 顧客ID | token  | ID      | CHAR(16)     |
| 名前   | token  | NAME    | VARCHAR(64)  |
| 住所   | string | ADDRESS | VARCHAR(256) |

* 道具

** 商品

IDは、ID欄で指定しています。

#+caption: 属性一覧
| 名前   | 型    | ID | カラム | SQL型       |
|--------+-------+----+--------+-------------|
| 商品ID | token | ○ | ID     | CHAR(16)    |
| 名前   | token |    | NAME   | VARCHAR(32) |
| 定価   | money |    | PRICE  | LONG        |

* 出来事

** 購入

IDは、特性欄で指定しています。

#+caption: 特性一覧
| 特性 | 名前   | 型    | 多重度 | 派生        | カラム      | SQL型    |
|------+--------+-------+--------+-------------+-------------+----------|
| ID   | 購入ID | token |        |             | ID          | CHAR(16) |
| 属性 | 日付   | date  |        |             | DATE        | DATE     |
| 関連 | 顧客   | 顧客  |      1 |             | CUSTOMER_ID | CHAR(16) |
| 属性 | 顧客名 | token |        | 顧客.名前   |             |          |
| 関連 | 商品   | 商品  |      1 |             | GOOD_ID     | CHAR(16) |
| 属性 | 数量   | int   |        |             | AMOUNT      | INT      |
| 属性 | 商品名 | token |        | 商品.名前   |             |          |
| 属性 | 単価   | money |        | 商品.定価   |             |          |
| 属性 | 総額   | money |        | 数量 * 単価 |             |          |

SimpleModelerを以下のように実行します。

$ sm -diagram sample.org

以下のクラス図の画像が生成されます。

2012年12月17日月曜日

MindmapModeling「チャネルの多様化で難題と化す予算の最適配分」

12月15日(土)に横浜モデリング勉強会(facebook group)を行いました。また、会場には(株)アットウェア様の会議室をお借りしました。参加された皆さん、アットウェア様、どうもありがとうございました。

この勉強会で、浅海が作成したモデルを紹介します。モデルはMindmapModelingの手法で作成しました。(勉強会で使用したチュートリアル)

ワークショップの流れ

モデリング勉強会はワークショップ形式で以下の作業を行います。

  • 雑誌記事から情報システムの企画書、提案書、RFPの元ネタとなるモデルを作成する。

その上で、「要求仕様確認、実装可能性確認、開発のベースとなるプログラムを自動生成するモデルを目指」します。詳細は「ワークショップの進め方 第2版」になります。

テーマ

モデリングの対象は、日経ビジネス誌の記事「チャネルの多様化で難題と化す予算の最適配分」です。

用語の収集と整理

まず用語の収集と整理します。

MindmapModelingに慣れてくると、用語がだいたいどこの枝に収まるのかわかるようになるので、用語を拾いながらラフなモデルを作っていきます。




今回の記事は、広告の効果を計測するメカニズム的なことが多く書かれていたので、このあたりを規則と登場人物を中心に分類しました。

クラス図

この段階でのマインドマップをSimpleModelerでクラス図化したものが以下になります。




いくつかモデルの島ができていますが、全体としてはまだばらばらです。

物語

次の作業は「物語」です。

モデルは中心軸がないと単なる「用語」の集りなのでまとまりがでてきません。何らかの目的を実現するための構造を抽出したいわけですが、この「目的」と「目的を実現するための構造」を掬いとるためのツールとして有効なのが「物語」です。オブジェクト・モデリングの概念ではビジネス・ユースケースということになります。

「物語」を中心軸と定め、「物語」のスコープで用語を取捨選択、組織化し、足りない用語を補っていきます。

その手順は:

  1. 物語の名前をつける。目的(goal)が明確になる名前がよい。
  2. 物語の主人公、相手役、脇役などの登場人物を定める。
  3. 物語で使用する道具を定める。
  4. 出来事または脚本の列として脚本を記述する。

となります。2の登場人物と3の道具は最初から完全なものはできないので暫定的なものを定め、4の脚本の作業を通して洗練させていきます。




「物語」として、「広告予算編成を最適化する」を設定し、この「物語」の作成を軸に、「出来事」の整理、「道具」の整理を進めました。

物語「広告予算編成を最適化する」は以下の出来事による脚本から構成されます。

  • マーケッティング施策を実行する
  • マーケッティング施策の結果を計測する
  • マーケッティング施策の結果を分析する
  • 予算配分する

さらに、各出来事を詳細化して、規則や道具を結びつけていきます。

今回の記事は内容がかなり絞りこまれていたので、モデリングの方向を問題領域の世界を記述するというよりシステム化を指向した詳細なモデルを記述するようにしてみました。

クラス図

この段階でのマインドマップをSimpleModelerでクラス図化したものが以下になります。




かなりクラス図らしくなってきました。ビジネス・ユースケース→イベント→アクター+リソースの流れが分かりやすく可視化されています。

ここまでの作業で時間切れとなりました。

ちょっと洗練

時間内に作ったマインドマップモデル+クラス図はSimpleModelerの機能不足もあって、出来事から規則に続く流れがクラス図上に表示できませんでした。

そこで、SimpleModelerに機能追加を行いながら、この点の記述の精度を上げたモデルが以下のものです。




クラス図

クラス図は以下になります。




SimpleModelingではアルゴリズムを記述するためのモデルとして、規則に加えてサービスを持っていますが、今回このサービスをマインドマップモデルでも使ってみました。

SimpleModelerで出来事から規則およびサービスへの関係は依存性(dependency)として記述しています。

ノート

オブジェクト・モデリングの鍵の一つは、静的構造を記述するドメイン・モデル(クラス図)と動的モデルをどのように関連付けていくのかという点です。

SimpleModelingでは、イベント(出来事)を起点にルールやサービスを呼び出すという点を軸の一つにしています。今回はよい機会なのでSimpleModelerにこのあたりの機能を追加してMindmapModelingからクラス図の生成の処理に盛りこんでみました。

具体的な記述方法、使い方は追々ブログで紹介していきたいと思います。

次回

次回はまだ確定していませんが1月19日(土)を予定しています。

詳細情報はfacebookグループ「横浜モデリング勉強会」を参照してください。

今回と同じく「ワークショップの進め方 第2版」の手順で、「雑誌記事から情報システムの企画書、提案書、RFPの元ネタとなるモデルを作成する」を行う予定です。

2012年12月14日金曜日

Scala Tips / Streamで採番(2)

Streamで採番では、Streamのfromメソッドを使って数値の連番による番号の採番を行いました。

場合によっては、採番する番号として「A00001」というような英数字と数字の組合せを取りたい場合があります。

このような場合は、カスタムでStreamを作ることになります。

といっても、これは簡単で以下のような再帰関数のイディオムを使います。

def nums(i: Int): Stream[String] = {
  "A%05d".format(i) #:: nums(i + 1)
}

使ってみる

それでは新しく作ったnums関数による採番用Streamを使ってみましょう。

採番した値の型はStringになるのNumberedNameを修正します。

case class NumberedName(name: String, number: String)

シーケンスに対して採番を行い値と採番した番号のTupleを返す関数は以下になります。Stream#fromの代わりにnums関数を使っています。

def f(names: Seq[String]): Seq[NumberedName] = {
  for ((n, c) <- names zip nums(1)) yield {
    NumberedName(n, c)
  }
}

実行

それでは実行してみましょう。

まずテスト用のListを定義します。

scala> val xs = List("a", "b", "c")
xs: List[java.lang.String] = List(a, b, c)

このListに対する関数fの実行結果は以下になります。

scala> f(xs)
res6: Seq[NumberedName] = List(NumberedName(a,A00001), NumberedName(b,A00002), NumberedName(c,A00003))

ごくごく簡単に使うことができますね。

ノート

再帰関数とStreamを使うことで、採番ロジックを関数として部品化することができました。この部品はSeqのzip関数でシーケンスと結びつけることができます。

こういった疎結合の関数部品を用意して、関数合成のテクニックで組合せて使うのが、関数型らしいプログラミングです。Streamの活用はそのような関数型プログラミングを行う上で重要なテクニックになっています。

諸元

  • Scala 2.9.2

2012年12月13日木曜日

Scala Tips / Streamで採番

名前に連番で番号を採番して関連付ける処理を考えます。採番する番号は、10から始めて10の間隔の昇順の数値とします。

名前と番号の対応を記述するケース・クラスNumberedNameを用意します。

case class NumberedName(name: String, number: Int)

引数で名前のシーケンスを受け取り、10から始めて10の間隔の昇順の数値を採番し、NumberedNameに名前と対にして格納する処理は、普通に考えると以下のようになるでしょう。

def f(names: Seq[String]): Seq[NumberedName] = {
  var c = 10
  for (n <- names) yield {
    val r = NumberedName(n, c)
    c += 10
    r
  }
}

手続き型プログラミング(Javaのメソッド実装を含む)では、よく出てくるロジックの形です。

実行

名前のリストを用意します。

scala> val xs = List("a", "b", "c")
xs: List[java.lang.String] = List(a, b, c)

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

scala> f(xs)
res0: Seq[NumberedName] = List(NumberedName(a,10), NumberedName(b,20), NumberedName(c,30))

関数型のアプローチ

関数型プログラミングでは副作用をできるだけ避けるのが正しいプログラミング・スタイルになります。この目印となるのが「var」による変数宣言で、Scalaプログラミングではこの「var」が出てくると注意信号です。「「var」は明確な意図がない限り使わない」というのが有効なプログラミング戦略になります。

この方針を取るとすると、前述の関数fは明らかに方針に反しています。ただ、副作用なしでこのロジックを記述するのは案外大変です。このような簡単なロジックが捌けないようでは、副作用なし、「不変」戦略によるプログラミングを行うことできません。

このような連番採取の反復処理を関数型プログラミングで記述するのに使えるのが無限シーケンスであるStreamです。

Streamを使って記述すると以下のようになります。

def f(names: Seq[String]): Seq[NumberedName] = {
  for ((n, c) <- names zip Stream.from(10, 10)) yield {
    NumberedName(n, c)
  }
}

Streamのfromメソッドでは、第一引数に開始の番号、第二引数に連番の間隔を指定すると、数値の無限シーケンスであるStreamが返ってきます。これをSeqのzipメソッドを使って引数のシーケンスと接続すると引数のシーケンスとStreamに格納された数値を使った、名前と番号のTupleのシーケンスができあがります。Streamは無限シーケンスですが、引数のSeqが有限であれば、Tupleのシーケンスはそちらの長さになるので問題はありません。

このTupleをfor式で「(n, c)」という形の変数に受け取ってケースクラスNumberedNameに値を設定すればOKです。

実行

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

scala> f(xs)
res1: Seq[NumberedName] = List(NumberedName(a,10), NumberedName(b,20), NumberedName(c,30))

この手の処理は「Stream+zipメソッド+for式の「(n, c)」」による記述が一つの形です。イディオムとして覚えておくとよいでしょう。

ノート

関数型プログラミングではStreamがかなり重要な部品になります。

以前「Streamで脱出」という話題でもStreamを取り上げました。

オブジェクト指向プログラミングでもjava.io.InputStreamといった形で無限を扱うためのテクニックとしてストリームの概念は出てきていますが、気軽に普段使いするようなものでもなく、IO入出力向けの用途限定テクニックという扱いでした。

一方、関数型プログラミングではごく気軽な普段使いのテクニックとして登場します。さらに、副作用を用いずプログラミングするには非常に有効なテクニックでもあり、そういう意味では関数型プログラミングの必須テクニックということができます。

諸元

  • Scala 2.9.2

2012年12月12日水曜日

ゆるふわScalaプログラミング・振り返り(6)

振り返り(5)では:

  • 可変分離⇔並行プログラミング
  • 不変⇔並列プログラミング

の組合せの中で「可変分離⇔並行プログラミング」について考えてみました。

不変⇔並列プログラミング

並列プログラミングの場合、リソースの更新は必須ではありませんから、可能な限り「不変」戦略を取りたいところです。

「不変」戦略による並列プログラミングの手法として以下のものが考えられます。

  • 並列コレクション
  • フューチャー・モナド

使い方が簡単なのは並列コレクションです。この場合、並列コレクションに対してmapコンビネータなどを用いて関数を合成してく形になります。しかし、この方法では、mapコンビネータを多段に結合した場合に各段毎に同期が発生してしまうために、並列度に制約が出てしまうという問題があります。

このあたりの問題はScalazの以下のスライドが詳しいです。

そこで、より本格的な並列プログラミングの基盤として期待したいのがフューチャー・モナドです。上のスライドにあるようにScalaz 6系ではPromiseモナドとして提供されています。Scala 2.10では、同等機能がFutureモナドとして基本ライブラリで提供されるので、本記事ではフューチャー・モナドと呼ぶことにします。

そこで、スライドの以下のページになります。



フューチャー・モナド(ScalazのPromiseモナド)を使って、並列プログラミングした場合の動作時間と並列動作の概念図を4ページを使って説明しています。ここで重要なのは、フューチャー・モナドの使い方はOptionモナドやListモナドといった他のモナドと全く同じでよいということです。普通にモナドを使ったMonadicプログラミングをしておけば、モナドとしてをフューチャー・モナドを使うことで、自動的に並列プログラミングになるわけです。

2012年12月11日火曜日

ゆるふわScalaプログラミング・振り返り(5)

振り返り(4)では、並行プログラミング/並列プログラミングの軸と、可変共用、可変分離、不変の軸を導入し、この2つの軸の相性の良い組合せと考えられる:

  • 可変分離⇔並行プログラミング
  • 不変⇔並列プログラミング

について簡単に考えてみました。

可変分離⇔並行プログラミング

アクター・プログラミングでは、「可変分離」戦略によって、アクター内に閉じたリソースに関しては競合を気にすることなく自由に更新することができます。

とはいえ、プログラムの信頼性、保守性を考えると可能な限り「不変」戦略を取るのがよいでしょう。また、個人的には一定のテクニックをマスターすれば、「不変」戦略の方が開発効率が高いと感じています。バグが出難いのが大きいですし、オブジェクトのライフサイクルに対する考慮をしなくてもよいのも大きなメリットです。

スライドにある以下の図は、オブジェクト指向と関数型を組み合わせたプログラミング・モデルです。オブジェクト指向の部分と関数型の部分を明確に分離し、オブジェクト指向の部分でリソースの更新を行います。そして、関数型の部分では「不変」戦略によって信頼性、保守性、開発効率のメリットを取りにいきます。



簡単な流れは以下のようになります。

  1. アクターがリクエストを受け取る。
  2. リクエストからリソースを更新するための更新指示書を計算。
  3. 更新指示書に従ってリソースを更新。
  4. 必要に応じてレスポンスを返す。

ここでは、ざっくり「更新指示書」と呼んでいますが、単なる値の場合もありますし、SQLのようなバッチ的更新プログラム、インタラクティブなインタープリタ・プログラムといったパターンがあります。

いずれにしても、アクター処理の大半を関数型的な「不変」戦略によって記述することで、信頼性、保守性、開発効率のメリットを享受することができます。

この方式の短所として、メモリ使用量が多くなる、実行速度が若干遅くなる、ということはありますが、今時のハードウェアではほとんど問題にならないと思われます。問題になるケースでも、問題箇所のみオブジェクト指向を使う、Javaを使う、という形で旧来の技術で最適化を行えばよいでしょう。

2012年12月10日月曜日

ゆるふわScalaプログラミング・振り返り(4)

振り返り(3)では並行(concurrent)プログラミングと並列(parallel)プログラミングの用語を導入しました。

続けて、並列/分散処理を記述するためのプログラミング技法を議論するためにもう一つの軸を導入します。

もう一つの軸

もう一つの軸とは以下の3つです。

  • 可変共用(shared mutability)
  • 可変分離(isolated mutability)
  • 不変(immutability)

このあたりはスライドの以下のページにまとめています。

プログラミングの方針では、不変が最上で、不変が難しい場合は可変分離、避けたいのが可変共用となります。

いわゆるマルチスレッド・プログラミングというと可変共用のカテゴリになるわけですが、これが「避けたい」プログラミング・モデルというのが重要です。

可変分離や不変のプログラミング・モデルは(現時点の)Javaでは可能ではありますが、あまり相性がよくありません。逆に、関数型言語と相性のよいプログラミング・モデルです。この点から、関数型言語が並行/並列プログラミングに向いているのではないかと予測できるわけです。

可変分離は、Scalaもサポートしているアクターによって実現できます。

不変は、言うまでもなく純粋関数型のプログラミング・モデルの前提条件ですし、関数型プログラミングの核となる概念です。

合わせてどうなるか

さて、並行プログラミングと並列プログラミングの軸、可変共用、可変分離、不変の軸の2つの軸を導入しました。

この2つの軸は直交するので、それぞれの組合せが可能ですが、相性の良い組み合わせの候補と考えられるのが以下の2つです。

  • 可変分離⇔並行プログラミング
  • 不変⇔並列プログラミング

まず、並行プログラミングは同時に発生する事象から駆動される処理が同一リソースを更新したいということが問題の発端なので、「不変」(のみ)では対処することができません。アクターを用いた「可変分離」でこの問題に対処するのが一つの解になります。

一方、並列プログラミングは1つの処理を分割して複数のCPU /コアで同時実行することで処理性能を向上させることが基本になるので、「不変」による実装が可能です。より好ましいのは「不変」>「可変分離」ですから、不変と可変分離の両方の選択肢が取れるのであれば不変を選ぶのが得策ということになります。

2012年12月7日金曜日

SmartDox 0.3.0

SmartDox 0.3.0をリリースしました。

本バージョンでは細かい機能拡張を行なっています。内容についてはブログで紹介していく予定です。

機能

SmartDox 0.3.0では以下のオプションを提供しています。

オプション機能
-html5HTML5生成(試験的)
-html4HTML4生成
-html3HTML3生成
-plainプレインテキスト生成
-pdfPDF生成
-latexLaTeX生成
-bloggerBlogger用のHTML生成

インストール

プログラムの配布は、Scala用のプログラム配布ツールconscriptを使っています。

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
$ dox -plain mydoc.dox

2012年12月6日木曜日

SimpleModeler 0.4.0-RC5

モデルコンパイラSimpleModeler 0.4.0-RC5をリリースしました。

マインドマップ(XMind)、SmartDox DSL、CSVからクラス図とJavaプログラムを生成する機能が実用フェーズとなっています。

次ぐらいに0.4.0正式版になる予定です。

機能

Simplemodeler 0.4.0-RC5では以下のオプションを提供しています。

オプション機能状況
projectプロジェクト生成α
importモデル移入α
convertモデル変換試験的
html仕様書生成α
javaJava生成
androidAndroid生成α
diagramクラス図生成
buildプロジェクトビルド試験的
gaejGoogle App Engine Java生成試験的
gaeGoogle App Engine Python生成試験的
gaeoGoogle App Engine Oil生成削除予定
grailsGrails生成試験的
g3g3生成試験的
asakusaAsakusa生成試験的

基本的にはマインドマップ(XMind)、SmartDox DSLとCSVからクラス図とJavaプログラムを生成する処理が実用フェーズになっています。その他の機能はα版または試験的実装の状態です。

インストール

プログラムの配布は、Scala用のプログラム配布ツールconscriptを使っています。

conscriptをインストールした後、以下のようにしてSimpleModelerをインストールします。

$ cs asami/simplemodeler

以下のコマンドがインストールされます。

sm
SimpleModelerコマンド
$ sm -version
Copyright(c) 2008-2012 ASAMI, Tomoharu. All rights reserved.
SimpleModeler Version 0.4.0-RC5 (20121206)
graphviz

-diagramオプションでクラス図を生成する場合は、graphvizのインストールが必要です。graphvizのインストール方法は以下を参照してください。

各プラットフォーム向けパッケージ管理ツールでもインストールできるようです。

Mac
http://www.macports.org/
Windows
http://sourceware.org/cygwinports/

使い方

マニュアルはまだありません。以前のバージョン用のものがありますが、機能が色々変わってしまったので一から見直す予定です。

リファレンスマニュアルとユーザーガイドの元ネタをこのブログで随時書いていきます。

CSV

Mind(マインドマップ)、SmartDox DSL、CSVからクラス図を生成することができます。

以下のCSVファイルをsample.csvとして用意します。

#actor,base
顧客
個人顧客,顧客
法人顧客,顧客
#resource,attrs,powers
商品,商品名;定価(long),商品区分(第1類;第2類;第3類)
#event,parts
購入する,顧客;商品

SimpleModelerを以下のように実行します。

$ sm -diagram sample.csv

以下のクラス図の画像が生成されます。

SmartDox

以下のSmartDoxファイルをsample.orgとして用意します。

#+title: Table

SmartDox DSLを使って記述したモデルの
サンプル文書です。

文書でサンプルモデルの定義をします。
本来は文書中の仕様記述の文書は
定義するモデルに対するものになります。

しかし、この文書ではSmartDox DSLの記述例として
SmartDox DSL文法の説明を記述することにします。

* サンプル文書の目的

このサンプル文書は表を中心にしてクラス定義するサンプルです。

登場人物、道具、出来事の各エンティティの種別の下に
顧客、商品、購入といった具象エンティティを節として
定義します。

そして、それらの節の下に属性一覧または特性一覧として
エンティティの属性や関連を記述していきます。

* 登場人物

** 顧客

IDの指定はありませんが、以下のルールで推測しています。

- 陽にID指定がない場合、先頭の属性の属性名が「id」(大文字可)で終わる場合はIDとみなす。

#+caption: 属性一覧
| 名前   | 型     | カラム  | SQL型        |
|--------+--------+---------+--------------|
| 顧客ID | token  | ID      | CHAR(16)     |
| 名前   | token  | NAME    | VARCHAR(64)  |
| 住所   | string | ADDRESS | VARCHAR(256) |

* 道具

** 商品

IDは、ID欄で指定しています。

#+caption: 属性一覧
| 名前   | 型    | ID | カラム | SQL型       |
|--------+-------+----+--------+-------------|
| 商品ID | token | ○ | ID     | CHAR(16)    |
| 名前   | token |    | NAME   | VARCHAR(32) |
| 定価   | money |    | PRICE  | LONG        |

* 出来事

** 購入

IDは、特性欄で指定しています。

#+caption: 特性一覧
| 特性 | 名前   | 型    | 多重度 | 派生        | カラム      | SQL型    |
|------+--------+-------+--------+-------------+-------------+----------|
| ID   | 購入ID | token |        |             | ID          | CHAR(16) |
| 属性 | 日付   | date  |        |             | DATE        | DATE     |
| 関連 | 顧客   | 顧客  |      1 |             | CUSTOMER_ID | CHAR(16) |
| 属性 | 顧客名 | token |        | 顧客.名前   |             |          |
| 関連 | 商品   | 商品  |      1 |             | GOOD_ID     | CHAR(16) |
| 属性 | 数量   | int   |        |             | AMOUNT      | INT      |
| 属性 | 商品名 | token |        | 商品.名前   |             |          |
| 属性 | 単価   | money |        | 商品.定価   |             |          |
| 属性 | 総額   | money |        | 数量 * 単価 |             |          |

SimpleModelerを以下のように実行します。

$ sm -diagram sample.org

以下のクラス図の画像が生成されます。

2012年12月5日水曜日

ゆるふわScalaプログラミング・振り返り(3)

メニーコアのプログラミングを考える上で重要なのは:

  • 並行(concurrent)プログラミング
  • 並列(parallel)プログラミング

の違いです。

この点をセッションでも触れようと思っていたのですが、結局スライドを作りそこねてしまいました。

この2つの用語はいろいろな意味で使われていて、場合によっては同じ意味で用いられるケースもあると思いますが、セッション内では以下の用法を想定しています。

並行プログラミング
同時並行に動作する複数の処理を複数のタスクによって同時に実行する
並列プログラミング
ひとつの処理を並列に動作する複数タスクの協調動作で実行する

たとえば、外部イベントで駆動される処理の場合、並行プログラミングは同時期に発生する複数の外部イベントに対応する処理が同時に動作するのに対して、並列プログラミングは1つの外部イベントに対応する処理が複数のCPU上で並列動作して高速に処理を完了する、というような違いになるかと思います。

ここでいう並行プログラミングはメニーコアでなくても普通に発生する問題で、今までもスレッド技術である程度うまく扱えていた問題です。Javaでいうとサーブレットの技術が相当します。

一方、メニーコア時代に入って問題となるのが一つの処理をメニーコア上で高速動作できないのか、という点です。たとえば、100の処理量のある処理を100コアを使って1の時間で実行できないのか、ということです。この問題は「同時並行に動作する複数の処理」を捌くためのプログラミング・モデルである「並行プログラミング」ではうまくさばけない、あるいは効果が限定的なのではないのか、という懸念につながります。

この観点から並列プログラミングに期待が集まるわけです。ボクも並列プログラミングに詳しいわけではないですが、関数型言語のモナドを使うとかなりうまく扱えそう、ということがScalaプログラミングを通じて分かってきました。

そういうわけで、今回のセッションのテーマの一つはモナドを使った並列プログラミングとなります。OFPによる新三種の神器の「モナド」の具体的な適用分野ですね。



2012年12月4日火曜日

ゆるふわScalaプログラミング・振り返り(2)

11月30日に開催されたBPStudy#63で「Scalaプログラミング・マニアックス」の振り返り第2弾です。

後から気づいた書き忘れの項目として 「Scalaを採用する理由」があります。具体的には以下の5つを考えていました。

  • 静的型付け
  • JavaVM
  • 関数型によるアルゴリズム記述力
  • メニーコア(並列プログラミング)
  • DSL

静的型付けとJavaVMは、個人的にプログラミング言語を選ぶ時の必須項目と思っているものです。

静的型付けはよいとして、JavaVM上で動作するプログラミング言語であるというのは地味に大事です。Javaで開発された膨大なクラスライブラリが再利用できること、異なったプラットフォーム間での可搬性、マルチスレッド動作の安定性、といった要素を考えると、余程のことがない限りJavaVM以外は選びにくいというのが個人的な考えです。

関数型言語はリスト処理の機能が充実しているのと、高階関数といった機能を使っていろいろな技が使えるので、手続き型言語よりもアルゴリズムの記述力は高くなります。

Javaの場合、処理の記述は「手続き型」ですから「関数型」が可能なScalaの方が記述力が高くなるわけです。

とはいえ、以上の点だけであれば、今までどおりJavaを使いつつ、Project Lambdaのような関数型系の記述方式の拡張を待つという選択もあります。拡張を待つ間、部分的にGroovyやxtendといった言語を併用して凌ぐという手もあるでしょう。

ただ、Javaに「関数型」的な機能を拡張する方式で克服が難しいのではないかというのが、後の2つ「メニーコア」と「DSL」です。

それはなぜか…ということでセッションのディテイルに入るような流れを考えていたわけです。

2012年12月3日月曜日

ゆるふわScalaプログラミング・振り返り

11月30日に開催されたBPStudy#63で「Scalaプログラミング・マニアックス」と題してお話させていただきました。



ゆるふわScalaプログラミング」 といった方向性を考えていたのですが、11月中にあまり時間を取ることができず、今まで作ったScalaに関するスライドの再構成という形にしました。ただ、Scalaプログラムの細かいところに踏み込まず、Scalaプログラミングの要素技術、特にJava系のオブジェクト指向プログラミングでは意識することのない技術に焦点を当てることを目指しています。

Javaなどオブジェクト指向プログラミングをひと通り知っているプログラマが「全く聞いたことのない技術ばかり!」と驚いてもらえたら成功です。

とはいえ、実際の所Scalaはオブジェクト側に倒してある言語なので、オブジェクト指向を主、関数型を従の塩梅で使うのがよく、あまり関数型方面ばかりを強調するのもミスリードの可能性もあります。

そうではあるのですが、今のタイミングでJavaからScalaへ乗り換えるべきである積極的な理由としては、Javaにはない新しい概念、新しいプログラミング・モデルを使うことで、新しい現実であるクラウド・アプリケーションに対応できる可能性が高まることです。このための新技術、特にオブジェクト指向プログラマが見たこともない技術に気づいてもらうのがセッションの狙いというわけです。

この観点から、クラウド・アプリケーションに必要な要素とScalaの言語機能の関係をまとめてみようと思っていたのですが、セッションの段階ではまだまとめきれませんでした。そこで、ブログの方でセッションを振り返りながらつらつら考えていきたいと思います。

2012年11月30日金曜日

MindmapModeling/SimpleModeler - 状態機械

オブジェクト指向モデリングでは、状態機械(state machine)が動的モデルの核となるモデル要素です。

状態機械はネスト状態をサポートした本格的なものをScala DSLとして組み込み済みです。しかし、Mindmap DSLとSmartDox DSLで定義できるようにはなっていないので、文法の拡張を行ない定義可能にする予定です。

まず、最初のターゲットとしてScala DSLのフルスペックの状態機械ではなく、Javaの属性として変数領域を確保するための情報を定義する範囲でのDSL化を行なっています。

MindmapModeling

状態機械のために、MindmapModelingのトップクラスの枝であるBOI構造枝に「状態機械」を追加しました。各状態機械はこの枝の下に配置します。



以下のようにしてクラス図を生成することができます。

$ sm -diagram statemachine.xmind

このマインドマップから生成されるクラス図です。3つの状態「開始」、「実行中」、「終了」を持つ状態機械「進行」が定義されています。



SmartDox DSL

上のMindmapModeling文法に対応するSmartDox DSLは以下になります。

#+title: 状態機械/StateMachine

* StateMachine

** 進行

#+caption: 状態
| name   |
|--------|
| 開始   |
| 実行中 |
| 終了   |

このDSLから以下のようにしてクラス図を生成することができます。

$ sm -diagram statemachine.org

生成されるクラス図は以下になります。




ノート

状態機械内の状態遷移を記述するSmartDox DSLおよびMindmapModelingの文法は現在検討中です。

最終的には、この状態機械モデルからJavaやScalaプログラムの生成まで行う予定です。

諸元

SimpleModeler Version 0.4.0-RC5-SNAPSHOT (20121126)

SimpleModeler 0.4.0-RC4から少し手を入れたバージョンなので、0.4.0-RC4とは出力されるクラス図が若干異なります。

2012年11月29日木曜日

MindmapModeling/SimpleModeler - 関連クラス

SimpleModelerで扱うメタモデルを設計する上での懸案事項の一つが多対多の関連と関連クラス(association class)の扱いです。

SimpleModeling/MindmapModelingのオブジェクト・モデルの中で多対多の関連や関連クラスをどう扱い、JavaやRDBMSにどうマッピングしていくのかという点は、テキストDSLでの効率的な記述方法の問題と相まって、あまり良い解を思いつきませんでした。

また、多対多の関連や関連クラスはリソースエンティティや普通のエンティティで代替するのが可能なことと、多対多の関連が出てくるところはモデリングを進めると属性を持った普通のクラスに伸びていくことが多いので、それほど強いニーズを感じていなかったということもあります。

ただ、以下の2つのニーズがあることがわかったので関連クラスをサポートすることにしました。

  • 既存のRDBMSのテーブルを扱う場合に、関連クラスを使えると便利
  • オブジェクト側に変更を加えず、ユースケースのニーズに応じて関連を追加できる方法があると便利

多対多の関連は、今のところ直接DSLで記述できるようにする予定はありませんが、関連クラスを使って実現することが可能です。

MindmapModeling

関連クラスは、MindmapModelingのトップクラスの枝であるBOI構造枝に「関連」を追加し、この下に配置します。



以下のようにしてクラス図を生成することができます。

$ sm -diagram associationClass.xmind

このマインドマップから生成されるクラス図です。トレイト・クラスが一つ定義されています。



SmartDox DSL

上のMindmapModeling文法に対応するSmartDox DSLは以下になります。

#+title: 関連クラス/Association Class

* 道具

** ブログ

** タグ

* 関連

** ブログタグ対応

#+caption: 関連
| 名前   | 型     | 多重度 |
|--------+--------+--------|
| ブログ | ブログ | *      |
| タグ   | タグ   | *      |

一点だけ、データ型を指定しているところが異なります。

このDSLから以下のようにしてクラス図を生成することができます。

$ sm -diagram associationClass.org

このマインドマップから生成されるクラス図です。トレイト・クラスが一つ定義されています。



この場合はデータ型をDSLで指定しているので、その指定もクラス図に反映されています。

諸元

SimpleModeler Version 0.4.0-RC5-SNAPSHOT (20121129)

SimpleModeler 0.4.0-RC4から少し手を入れたバージョンなので、0.4.0-RC4とは出力されるクラス図が若干異なります。

2012年11月28日水曜日

MindmapModeling/SimpleModeler - トレイトの使用方法

昨日「MindmapModeling/SimpleModeler - トレイト」のトレイトを実際に使う場合の文法について説明します。

MindmapModelingの使い方は以下になります。

このモデルでは「特色」(トレイト)として以下のものを定義しています。

  • タグ付け可能
  • 画像貼付け可能

そして、この2つのトレイトをミックスインした道具(resouce entity)「ブログ記事」を定義しています。「ブログ記事」の構造枝「特色」の下に2つの特色「タグ付け可能」と「画像貼付け可能」を配置しています。このことによって「ブログ記事」に「タグ付け可能」と「画像貼付け可能」がミックスインされます。




このマインドマップモデルから以下のようにしてクラス図を生成することができます。

$ sm -diagram trait.xmind

生成されたクラス図は以下になります。2つのトレイト「タグ付け可能」と「画像貼付け可能」が定義されており、リソース「ブログ記事」がこの2つのトレイトをミックスインしています。




Java

SimpleModelerでは「トレイト・モデリング」で説明したモデリングのトレイトのJavaマッピングをサポートしています。

先ほどのMindmapModelingのモデルから以下のようにしてJavaプログラムを生成します。

$ sm -java trait.xmind

Javaにはトレイトがないので、インタフェースとして生成されます。トレイト「タグ付け可能」に対応するインタフェース(の先頭部分)は以下のものになります。

public interface タグ付け可能 {
    public static final String PROPERTY_タグ = "タグ";
    public String getタグ();
    public void setタグ(String タグ);
}

リソース「ブログ記事」に対応するJavaクラス「ブログ記事」(の先頭部分)は以下のものになります。

@Entity
public class ブログ記事 implements タグ付け可能, 画像貼付け可能 {
    public static final String PROPERTY_ID = "id";
    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    @Basic(fetch=FetchType.EAGER)
    @Column(nullable=false)
    protected String タグ;
    @Basic(fetch=FetchType.EAGER)
    @Column(nullable=false)
    protected String リンク;

トレイトの実装部がクラスの中に埋め込まれるのと同時に、インタフェース「タグ付け可能」と「画像貼付け可能」がインプリメントされています。このことによって、事実上トレイトをミックスインしたクラスとして使用することができるわけです。

SmartDox DSL

上のMindmapModeling文法に対応するSmartDox DSLは以下になります。一点だけ、データ型を指定しているところが異なります。

#+title: 特色/Trait

* 特色

** タグ付け可能

#+caption: 属性
| name | type  |
|------+-------|
| タグ | token |

** 画像貼付け可能

#+caption: 属性
| name   | type |
|--------+------|
| リンク | link |

* 道具

** ブログ記事

#+caption: 性質一覧
| 項目 | 値                          |
|------+-----------------------------|
| 特色 | タグ付け可能,画像貼付け可能 |

#+caption: 属性
| name     | type   |
|----------+--------|
| タイトル | string |
| 内容     | text   |

このDSLから以下のようにしてクラス図を生成することができます。

$ sm -diagram trait.org

このSmartDox DSLから生成されるクラス図は以下になります。




諸元

  • SimpleModeler Version 0.4.0-RC5-SNAPSHOT (20121127)

SimpleModeler 0.4.0-RC4から少し手を入れたバージョンなので、0.4.0-RC4とは出力されるクラス図が若干異なります。

2012年11月27日火曜日

MindmapModeling/SimpleModeler - トレイト

トレイト・モデリング」でも取り上げたトレイトは、プログラミングのみならずモデリングでも非常に有効です。
このトレイトをSimpleModelerに組み込んでみました。
これに関連して、MindmapModelingではあまり使う機会はありませんが一応文法としても定義しています。トレイト(trait)はマインドマップ的には「特色」という用語を当てています。

以下のようにしてクラス図を生成することができます。
$ sm -diagram trait.xmind
このマインドマップから生成されるクラス図です。トレイト・クラスが一つ定義されています。



SmartDox DSL

上のMindmapModeling文法に対応するSmartDox DSLは以下になります。
#+title: 特色/Trait

* Trait

** タグ付け可能

#+caption: 属性
| name | type  |
|------+-------|
| タグ | token |
一点だけ、データ型を指定しているところが異なります。
このDSLから以下のようにしてクラス図を生成することができます。
$ sm -diagram trait.org
このマインドマップから生成されるクラス図です。トレイト・クラスが一つ定義されています。


この場合はデータ型をDSLで指定しているので、その指定もクラス図に反映されています。

諸元

SimpleModeler Version 0.4.0-RC5-SNAPSHOT (20121126)
SimpleModeler 0.4.0-RC4から少し手を入れたバージョンなので、0.4.0-RC4とは出力されるクラス図が若干異なります。

2012年11月26日月曜日

MindmapModeling/SimpleModeler - 要約の文法

MindmapModeling/SimpleModeler - 要約」で導入した「要約(summary)」のMindmapModelingの文法は以下になります。


以下のようにしてクラス図を生成することができます。
$ sm -diagram summary.xmind
このマインドマップから生成されるクラス図です。要約クラスが一つ定義されています。

SmartDox DSL

上のMindmapModeling文法に対応するSmartDox DSLは以下になります。
#+title: 要約/Summary

* Summary

** ランキング

#+caption: 属性
| name | type  |
|------+-------|
| 名前 | token |
| 得点 | int   |
一点だけ、データ型をしているところが異なります。
このDSLから以下のようにしてクラス図を生成することができます。
$ sm -diagram summary.org
このマインドマップから生成されるクラス図です。要約クラスが一つ定義されています。




この場合はデータ型をDSLで指定しているので、その指定もクラス図に反映されています。

諸元

SimpleModeler Version 0.4.0-RC5-SNAPSHOT (20121126)
SimpleModeler 0.4.0-RC4から少し手を入れたバージョンなので、0.4.0-RC4とは出力されるクラス図が若干異なります。

2012年11月22日木曜日

MindmapModeling/SimpleModeler - 要約

MindmapModeling「1回のメール配信で売り上げ数千万アップの驚異」」で、MindmapModelingの文法を拡張中であることを説明しました。正確にはSimpleModelingのメタモデルの拡張を行い、MindmapModelingの文法への反映と、SimpleModelerでの実装を行いました。

その文法拡張の一つが「要約(summary)」です。

summaryテーブルという考え方自体は昔からデータモデリングで使われていますし、「上流工程UMLモデリング」でもモデル要素としてはあげていました。これを第一級のモデル要素としてMindmapModelingやSimpleModelerで意識して扱うのが妥当かどうかという点が長年の懸案事項でした。

「要約」はクラウド・アプリで必須、という結論を出したのが昨年末になりますが、やっと実装することができました。

関連する記事:

とはいえ、今回はMindmapModelingに「要約」のBOI構造枝を追加したのと、SimpleModelerで扱えるようにするところまでです。

「要約」のあるところデータフローあり。「要約」データは、オンラインからのSQLの問合せでは実用的な応答速度を出せないような大規模演算が後ろに控えているのが普通です。この実装はバッチ処理になります。このバッチ処理の設計と実装にはデータフローが有効です。

データフローの記述方法をどうするのかというのが、これまた懸案事項だったのですが、この基本的なアイデアを思いついたのが、今回MindmapModeling/SimpleModelerに「要約」を入れることにした直接の動機になっています。

こちらは実装にもう少し時間がかかりそうですが、ある程度形が見えてきたらブログで紹介したいと思います。

2012年11月21日水曜日

SimpleModeler 0.4.0-RC4

モデルコンパイラSimpleModeler 0.4.0-RC4をリリースしました。

これは、11月17日に開催した横浜モデリング勉強会の前後に拡張した機能をまとめたものです。

マインドマップ(XMind)、SmartDox DSL、CSVからクラス図とJavaプログラムを生成する機能が実用フェーズとなっています。

また、扱えるメタモデルにいくつか拡張を行いました。ブログで紹介していく予定です。

0.4.0正式版はバグフィックスおよび新機能の動作確認が取れた後リリース予定です。

機能

Simplemodeler 0.4.0-RC4では以下のオプションを提供しています。

オプション機能状況
projectプロジェクト生成α
importモデル移入α
convertモデル変換試験的
html仕様書生成α
javaJava生成
androidAndroid生成α
diagramクラス図生成
buildプロジェクトビルド試験的
gaejGoogle App Engine Java生成試験的
gaeGoogle App Engine Python生成試験的
gaeoGoogle App Engine Oil生成削除予定
grailsGrails生成試験的
g3g3生成試験的
asakusaAsakusa生成試験的

基本的にはマインドマップ(XMind)、SmartDox DSLとCSVからクラス図とJavaプログラムを生成する処理が実用フェーズになっています。その他の機能はα版または試験的実装の状態です。

インストール

プログラムの配布は、Scala用のプログラム配布ツールconscriptを使っています。

conscriptをインストールした後、以下のようにしてSimpleModelerをインストールします。

$ cs asami/simplemodeler

以下のコマンドがインストールされます。

sm
SimpleModelerコマンド
$ sm -version
Copyright(c) 2008-2012 ASAMI, Tomoharu. All rights reserved.
SimpleModeler Version 0.4.0-RC4 (20121121)
graphviz

-diagramオプションでクラス図を生成する場合は、graphvizのインストールが必要です。graphvizのインストール方法は以下を参照してください。

各プラットフォーム向けパッケージ管理ツールでもインストールできるようです。

Mac
http://www.macports.org/
Windows
http://sourceware.org/cygwinports/

使い方

マニュアルはまだありません。以前のバージョン用のものがありますが、機能が色々変わってしまったので一から見直す予定です。

リファレンスマニュアルとユーザーガイドの元ネタをこのブログで随時書いていきます。

CSV

Mind(マインドマップ)、SmartDox DSL、CSVからクラス図を生成することができます。

以下のCSVファイルをsample.csvとして用意します。

#actor,base
顧客
個人顧客,顧客
法人顧客,顧客
#resource,attrs,powers
商品,商品名;定価(long),商品区分(第1類;第2類;第3類)
#event,parts
購入する,顧客;商品

SimpleModelerを以下のように実行します。

$ sm -diagram sample.csv

以下のクラス図の画像が生成されます。

SmartDox

以下のSmartDoxファイルをsample.orgとして用意します。

#+title: Table

SmartDox DSLを使って記述したモデルの
サンプル文書です。

文書でサンプルモデルの定義をします。
本来は文書中の仕様記述の文書は
定義するモデルに対するものになります。

しかし、この文書ではSmartDox DSLの記述例として
SmartDox DSL文法の説明を記述することにします。

* サンプル文書の目的

このサンプル文書は表を中心にしてクラス定義するサンプルです。

登場人物、道具、出来事の各エンティティの種別の下に
顧客、商品、購入といった具象エンティティを節として
定義します。

そして、それらの節の下に属性一覧または特性一覧として
エンティティの属性や関連を記述していきます。

* 登場人物

** 顧客

IDの指定はありませんが、以下のルールで推測しています。

- 陽にID指定がない場合、先頭の属性の属性名が「id」(大文字可)で終わる場合はIDとみなす。

#+caption: 属性一覧
| 名前   | 型     | カラム  | SQL型        |
|--------+--------+---------+--------------|
| 顧客ID | token  | ID      | CHAR(16)     |
| 名前   | token  | NAME    | VARCHAR(64)  |
| 住所   | string | ADDRESS | VARCHAR(256) |

* 道具

** 商品

IDは、ID欄で指定しています。

#+caption: 属性一覧
| 名前   | 型    | ID | カラム | SQL型       |
|--------+-------+----+--------+-------------|
| 商品ID | token | ○ | ID     | CHAR(16)    |
| 名前   | token |    | NAME   | VARCHAR(32) |
| 定価   | money |    | PRICE  | LONG        |

* 出来事

** 購入

IDは、特性欄で指定しています。

#+caption: 特性一覧
| 特性 | 名前   | 型    | 多重度 | 派生        | カラム      | SQL型    |
|------+--------+-------+--------+-------------+-------------+----------|
| ID   | 購入ID | token |        |             | ID          | CHAR(16) |
| 属性 | 日付   | date  |        |             | DATE        | DATE     |
| 関連 | 顧客   | 顧客  |      1 |             | CUSTOMER_ID | CHAR(16) |
| 属性 | 顧客名 | token |        | 顧客.名前   |             |          |
| 関連 | 商品   | 商品  |      1 |             | GOOD_ID     | CHAR(16) |
| 属性 | 数量   | int   |        |             | AMOUNT      | INT      |
| 属性 | 商品名 | token |        | 商品.名前   |             |          |
| 属性 | 単価   | money |        | 商品.定価   |             |          |
| 属性 | 総額   | money |        | 数量 * 単価 |             |          |

SimpleModelerを以下のように実行します。

$ sm -diagram sample.org

以下のクラス図の画像が生成されます。

2012年11月20日火曜日

MindmapModeling「1回のメール配信で売り上げ数千万アップの驚異」

11月17日(土)に横浜モデリング勉強会(facebook group)を行いました。また、会場には(株)アットウェア様の会議室をお借りしました。参加された皆さん、アットウェア様、どうもありがとうございました。

この勉強会で、浅海が作成したモデルを紹介します。モデルはMindmapModelingの手法で作成しました。(勉強会で使用したチュートリアル)

ワークショップの流れ

モデリング勉強会はワークショップ形式で以下の作業を行います。

  • 雑誌記事から情報システムの企画書、提案書、RFPの元ネタとなるモデルを作成する。

その上で、「要求仕様確認、実装可能性確認、開発のベースとなるプログラムを自動生成するモデルを目指」します。詳細は「ワークショップの進め方 第2版」になります。

テーマ

モデリングの対象は、日経ビジネス誌の記事「1回のメール配信で売り上げ数千万アップの驚異 - 良品計画のWebサイト『MUJI.net』の秘密を聞く(前編)」です。タイトルがキャッチーでよいですね。メールによるO2Oも旬のネタといえます。

用語の収集と整理

まず用語の収集と整理します。

MindmapModelingに慣れてくると、用語がだいたいどこの枝に収まるのかわかるようになるので、用語を拾いながらラフなモデルを作っていきます。



今回の記事は、色々な規則的やノウハウ的なことが多く書かれていたので、これらの記述は「規則」に分類しました。

登場人物の「顧客」は常識的な線でモデル化します。

ポイントとなりそうなのが出来事です。メールを使ったクーポンの発行がこのモデルの軸になりそうです。

クラス図

この段階でのマインドマップをSimpleModelerでクラス図化したものが以下になります。




顧客の島とイベントの島ができていますが、全体としてはまだばらばらです。

物語

次の作業は「物語」です。

モデルは中心軸がないと単なる「用語」の集りなのでまとまりがでてきません。何らかの目的を実現するための構造を抽出したいわけですが、この「目的」と「目的を実現するための構造」を掬いとるためのツールとして有効なのが「物語」です。オブジェクト・モデリングの概念ではビジネス・ユースケースということになります。

「物語」を中心軸と定め、「物語」のスコープで用語を取捨選択、組織化し、足りない用語を補っていきます。

その手順は:

  1. 物語の名前をつける。目的(goal)が明確になる名前がよい。
  2. 物語の主人公、相手役、脇役などの登場人物を定める。
  3. 物語で使用する道具を定める。
  4. 出来事または脚本の列として脚本を記述する。

となります。2の登場人物と3の道具は最初から完全なものはできないので暫定的なものを定め、4の脚本の作業を通して洗練させていきます。




「物語」として、「メールの店舗売上効果を測定する」を設定し、この「物語」の作成を軸に、「出来事」の整理、「道具」の整理を進めました。

出来事はあくまでもイベントの記述に特化して、クーポンは道具の方に分離しました。

また、今回新しいBOI構造枝として「要約」を追加しました。これは、「MindmapModelingと集合知(7) - クラウド拡張」などでクラウド・アプリ向けのメタモデルの拡張を説明してきましたが、この中の「サマリー」に相当するものです。

出来事の発生によって道具の状態が遷移していくのが、モデルの基本的な振舞いですが、「メールの店舗売上効果を測定する」という形で測定を行う必要があるので、測定対象のエンティティが必要になります。この測定対処のエンティティとして「要約」を用意しました。要約はバッチ処理で一定期間内のイベントの発生結果を集約する処理に用います。

クラス図

この段階でのマインドマップをSimpleModelerでクラス図化したものが以下になります。




だいぶまとまってきた感じです。クラス図を見ると、ユースケースから各種イベントをへて顧客に至る関係は確保できたことが分かります。その一方で、クーポンとイベントの関係がまだ設定できていません。

このあたりの関係はクラス図にしてみるとよく分かりますね。

一点、ビジネス・ユースケース「メールの店舗売上効果を計測する」からビジネス・タスク「BTメールの店舗売上効果を計測する」への呼出しをinclude依存性で記述する図になっています。ビジネス・タスク「BTメールの店舗売上効果を計測する」は内部的に自動生成したものですが、こういった図の場合は冗長なので自動生成させないようにしたいと思います。

ちょっと洗練

上の「物語」の作業からあまり時間が取れなかったので、道具にあるクーポン周りをブラッシュアップしました。




クラス図

クラス図は以下になります。



ここまでの作業で時間切れとなりました。

上のモデルから引き続いて出来事と道具の間の関係が設定できていない状態です。また、「メールの店舗売上効果を計測する」の要となる、RFM(Recerency, Frequency, Monetary)を計測対象とした計測の振舞いをクラス図の上で表現するところまで持っていけませんでした。

やるべき事はだいたい見えてきた感じです。作業を続けるとすると、まずは上記2つのポイントを埋めていくことになります。

ノート

今回のモデリングでは「要約」の他にいくつか機能拡張を行いました。機能拡張についてはSimpleModelerでの使い方を含めて別途説明したいと思います。

次回

次回は12月15日(土)です。

詳細情報はfacebookグループ「横浜モデリング勉強会」を参照してください。

今回と同じく「ワークショップの進め方 第2版」の手順で、「雑誌記事から情報システムの企画書、提案書、RFPの元ネタとなるモデルを作成する」を行う予定です。

2012年11月19日月曜日

Scala Tips / Seq, Function1, PartialFunction, Option

MapがFunction1かつPartialFunctionというのが案外盲点になりますが、SeqすなわちList, Stream, VectorもFunction1かつPartialFunctionというのも見過ごしがちです。

以下の関数fを考えます。「Intを引数に取りStringを返す関数」を第一引数に、Int値を第二引数に取り、Int値に対応するStringを返します。

def f(a: Int => String)(b: Int): String = {
  a(b)
}

準備

Seqの例としてListを定義します。

scala> val l = List("zero", "one", "two", "three")
l: List[java.lang.String] = List(zero, one, two, three)

ついでにMapも定義しておきます。

cala> val m = Map(4 -> "four", 5 -> "five")
m: scala.collection.immutable.Map[Int,java.lang.String] = Map(4 -> four, 5 -> five)

関数fの第一引数にListを指定するとList(Seq)はFunction1のためそのまま普通に動作します。

ListのインデックスがFunction1の引数に対応しており、引数に指定された数値に対応するListの内容を返します。

scala> f(l)(3)
res6: String = three

Map, Function1, PartialFunction, Option」で説明したようにMapもFunction1なので普通に動作します。

scala> f(m)(5)
res7: String = five

Listの範囲外の数値を指定すると例外が発生します。

scala> f(l)(10)
java.lang.IndexOutOfBoundsException: 10
 at scala.collection.LinearSeqOptimized$class.apply(LinearSeqOptimized.scala:51)
 at scala.collection.immutable.List.apply(List.scala:76)
 at scala.collection.immutable.List.apply(List.scala:76)
 at .f(<console>:12)
 at .<init>(<console>:14)
 at .<clinit>(<console>)
 at .<init>(<console>:11)
 at .<clinit>(<console>)
 at $print(<console>)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704)
 at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920)
 at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43)
 at scala.tools.nsc.io.package$$anon$2.run(package.scala:25)
 at java.lang.Thread.run(Thread.java:680)

Option化

Listの範囲外の数値を指定された時にもきちんと動作させるためにはFunction1ではなくPartialFunctionを使用します。PartialFunctionはliftメソッドでOption化されたFunction1に変換されるので、これを利用します。

Option化した関数fは以下になります。

def f(a: PartialFunction[Int, String])(b: Int): Option[String] = {
  a.lift(b)
}

List(Seq)もMapもPartialFunctionなので、そのまま関数fの引数に指定することができます。

動作結果は以下のとおりです。

scala> f(l)(3)
res9: Option[String] = Some(three)

scala> f(m)(5)
res10: Option[String] = Some(five)

scala> f(l)(10)
res11: Option[String] = None

PartialFunctionの合成

PartialFunctionはorElseを使って合成するのが、よく出てくるテクニックです。

関数fの引数として、lとmをorElseで合成したものを指定すると以下のように動作します。

scala> f(l orElse m)(3)
res12: Option[String] = Some(three)

scala> f(l orElse m)(5)
res13: Option[String] = Some(five)

scala> f(l orElse m)(10)
res14: Option[String] = None

Int値が0から3の場合は変数lのListに、4と5の場合は変数mのMapが対応する値を返します。それ以外の数値の場合は対応できるPartialFunctionがないためNoneが返ります。

ノート

Map, Function1, PartialFunction, Option」のMapも、今回のListも、Function1かつPartialFunctionであるというのが関数型プログラミング的には重要なポイントです。

関数型プログラミングでは、ファンクタの対象となるA→Bの関数、モナドの対象となるA→M[B]の関数(Mはモナド)が非常に重要な意味を持ちます。この形をした関数を部品として合成していくのが関数型プログラミングの基本的な考え方になるためです。

MapやSeqはFunction1つまりA→Bの関数です。またPartialFunctionであることはliftメソッドでA→Option[B]というA→M[B]の形に持ち込めます。つまり、MapやSeq特有の機能は持ちながら、いざとなればA→BやA→M[B]の関数としても使える点がプログラミングでの選択肢を広げることにつながっているわけです。

諸元

  • Scala 2.9.2

2012年11月16日金曜日

Scala Tips / Emacs

ScalaプログラミングはなんといってもEmacs+Ensimeですね!

先週サブノート目的でMacBook Air 11inchを入手したのですが、Emacs+Ensimeで満ち足りてしまって、まだEclipseは入れていない状況です。

Emacsのどこがよいかというと、まずテキストエディタとしての基本機能、シェルモードやディレクトリモードといったシェル機能が充実している点が挙げられますが、なんといってもelispで比較的簡単にカスタマイズができるのがよいですね。

カッコの捌き方

Scalaプログラミングをする時の生産性に影響があるのが「{」「}」の捌き方です。たとえば:

abc {

といれると、自動的に括弧を開いてくれてカーソルを所定の位置に持って行ってくれると便利です。

abc {
  ←カーソル
}

もちろん、これぐらいの自動化はどのエディタにもついていると思いますが、問題なのは「abc { }」と打った時に自動的・強制的に上の形になってしまうと、逆に「abc { x => x + 1 }」と書きたかった時に手戻りが発生してしまいます。

このような問題があるため、キーの入力で自動的に上記のような整形をする機能(Emacsのscala-modeではscala-mode-feature-electric)は案外使いづらく、ボクもたいてい切ってしまいます。

abc {
  ←カーソル
}

にしたい時と

abc { x => x + 1 }

にしたい時のどちらにも対応できる入力方法が欲しいところです。

カスタマイズ

この問題に対処するために、以下のelispの関数を作ってみました。

(defun my-scala-newline(arg)
  (interactive "p")
  (cond ((scala-in-multi-line-comment-p)
  (scala-newline))
 ((char-equal ?\} (following-char))
  (let (killed)
           (newline-and-indent)
           (newline-and-indent)
           (forward-char)
    (setq killed (not (my-end-of-line-p)))
    (if killed (kill-line))
           (previous-line)
           (indent-for-tab-command)
    (if killed (yank))))
 (t
  (newline-and-indent))))

.emacsまたはinit.elへの設定は以下になります。

(require 'scala-mode-feature-electric)

(setq scala-mode-feature:electric-expand-delimiters-list '(?\{))

(add-hook 'scala-mode-hook
          (function (lambda ()
        (scala-mode-feature-electric-mode)
          (define-key scala-mode-map "\r" 'my-scala-newline)

まずscala-mode-feature:electric-expand-delimiters-listで「{」のみを有効にするように設定しています。こうすることによって:

abc {

と打つと自動的に以下のようになります。

abc { }←カーソル

ここからの動きがポイントですが、先程のmy-scala-newline関数がReturnに設定されていると、この場所でReturnを押すことで、以下の形に整形されカーソルも所定の位置に移動します。

abc {
  ←カーソル
}

カーソルの位置が他の場所の場合、通常の改行キーの動作をするので、不必要な整形が行われません。「abc { x => x + 1 }」と書きたい時はそのまま入力をしていけばよいわけですね。

括弧を追加する場合の捌き方

my-scala-newline関数は、よく出てくるコーディングパターンをサポートするためにもう一工夫しています。

以下のように一行に書いていた文を:

abc xyz

括弧を入れて複数行に分割したい時がよくあります。

abc {
  xyz
}

まず、最初の状態で「abc 」の場所で「{」を打つと以下のようになります。カーソルは「}」の上になります。

abc { }xyz

ここでReturnを押下すると、「}」の後ろのxyzが自動的に括弧内に移動し、カーソルも所定の位置に移動します。

abc {
  xyz←カーソル
}

簡単なカスタマイズですが、なかなか効果抜群です。

2012年11月15日木曜日

ScalaTips / Map, Function1, PartialFunction, Option

クライアントから受け取ったリクエストや定義ファイルの情報などをMapに格納して、プログラム内で持ちまわることはよくあります。

このような場合、以下のように関数の引数にMapを渡します。

def f(config: Map[String, String]) {
  println(config("name"))
  println(config("url"))
}

以下のようなMapがある場合:

scala> val map = Map("name" -> "Taro", "url" -> "http://www.example.com")
map: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(name -> Taro, url -> http://www.example.com)

関数fの動作は以下のようになります。

scala> f(map)
Taro
http://www.example.com

MapはFunction1

JavaだとMapを受け取るメソッドを書いたところで終了ですが、Scalaの場合MapはFunction1という特徴があるので、もう少し応用範囲が広がります。

以下の関数fでは引数がMapではなくFunction1になっています。

def f(config: String => String) {
  println(config("name"))
  println(config("url"))
}

ScalaのMapはFunction1でもあるので、以下のようにこの関数にMapを指定することができます。

scala> f(map)
Taro
http://www.example.com

関数fの設計を考える場合、Function1がMapを包含しているわけですから、Function1を引数に取ったほうが応用範囲が広がり、より望ましい選択と言えます。

値がない場合を考慮

関数の引数にMapを取る場合、Mapの要素として指定した値がないことを考慮したい場合があります。この時はOptionを返すgetメソッドを用いて処理を切り分けます。

def f(config: Map[String, String]) {
  config.get("name").foreach(println)
  config.get("url").foreach(println)
}

値がある場合は以下のようになります。

scala> f(map)
Taro
http://www.example.com

値がない場合もきちんと動作します。

scala> f(Map.empty)

PartialFunction

値がない場合を扱うことができる関数としてScalaではPartialFunctionを提供しています。PartialFunctionのisDefineAtメソッドとapplyメソッドを駆使すると以下のように値がない場合の切り分け処理を記述することができます。

def f(config: PartialFunction[String, String]) {
  if (config.isDefinedAt("name")) {
    println(config("name"))
  }
  if (config.isDefinedAt("url")) {
    println(config("url"))
  }
}

ScalaのMapはFunction1であると同時にPartialFunctionでもあります。このため、この関数fにもそのまま指定することができます。

scala> f(map)
Taro
http://www.example.com

値がないときも無事動作しました。

scala> f(Map.empty)

PartialFunctionをFunction+Optionにlift

先ほどの関数fは、PartialFunctionの扱いが手続き型チックだったので、もう少し関数型っぽくしてみます。

具体的にはliftメソッドを用いて、PartialFunction[String, String]をFunction1[String, Option[String]]に持ち上げます。

def f(config: PartialFunction[String, String]) {
  val c: String => Option[String] = config.lift
  c("name").foreach(println)
  c("url").foreach(println)
}

実装は変わりましたが、インタフェースは変わらないのでMapを指定しても同じように動作します。

scala> f(map)
Taro
http://www.example.com

scala> f(Map.empty)

Option

今度は逆に、関数fの引数がFunction1[String, Option[String]]だった場合です。

def f(config: String => Option[String]) {
  config("name").foreach(println)
  config("url").foreach(println)
}

この場合は、Map[String, String]を指定するとエラーになってしまいます。

scala> f(map)
<console>:10: error: type mismatch;
 found   : scala.collection.immutable.Map[java.lang.String,java.lang.String]
 required: String => Option[String]
              f(map)
                ^

ここで登場するのがPartialFunctionのliftメソッドです。MapもPartialFunctionなので、このliftメソッドを使ってFunction1[String, Option[String]]に持ち上げることができます。

以下のように無事関数fに適用できました。

scala> f(map.lift)
Taro
http://www.example.com

ノート

日々のScalaプログラミングでよく出てくるMap, Function1, PartialFunction, Optionの連携を簡単にまとめてみました。

MapがFunction1かつPartialFunctionであるということは案外盲点で、このことを知っておけば関数のシグネチャでMapより応用範囲の広いFunction1やPartialFunctionを選択できるようになります。

また、Mapが出てくるような局面では値がない場合があることが普通なので、Function1よりもPartialFunctionがより望ましい選択になります。PartialFunctionが出てくるとOptionの活用も視野に入ってきます。

関数の引数にPartialFunctionを使うのか、Function1+Optionを使うのかはケースバイケースですが、Mapのliftメソッドの存在を知っておけば、どちらがきた場合でも対処できます。

諸元

  • Scala 2.9.2

2012年11月14日水曜日

SimpleModeler: Scala vs Java

SimpleModelerで生成するターゲットのプログラミング言語は悩みどころです。趣旨としてはScalaを主に考えたいところですが、現時点では時期尚早ではないかということでJavaを中心にした展開を考えています。

Javaはプログラミング言語としての安定性やプログラマ人口が多いのが非常に大きいですが、それに加えてJava EE系技術の成熟は侮れません。

地味に以下の様な技術が実用化されています。

これらの機能をScalaから使うこともできなくはないですが、ボクが試した範囲では必ずしも完全な連携ができるわけではないようです。(参考「ScalazでBean Validation」)

以上の検討の結果、以下のようなアーキテクチャを採用することにしました。



まず土台はJavaを採用します。この上にJava向けのファサード・コードとScala向けのサファード・コードを生成します。

Scala向けのサファード・コードではcase classを中心とした、関数型プログラミング向けのデータ構造を中心にしてJavaのコードを隠蔽する形になります。

2012年11月13日火曜日

ゆるふわScalaプログラミング(2) - 文脈の整理

11月30日に開催されるBPStudy#63のセッション「Scalaプログラミング・マニアックス」のネタ整理です。

内容は具体的なプログラミングの話ではなくて、「ゆるふわ」ということでScalaプログラミングをする中で分かってきたことの情報共有が趣旨です。その切り口として、クラウド時代のソフトウェア開発の文脈を整理してみます。

キーワード

前回はいくつかキーワードを挙げましたが、これに加えてDSLと並行/並列プログラミングといったところがScalaプログラミングの道具立てになります。改めて並べてみると以下になります。

  • トレイト
  • モナド
  • 型クラス
  • 代数的構造
  • 代数的データ構造
  • 永続データ構造
  • DSL
  • 並行/並列プログラミング

文脈

問題はこれらの機能が、クラウド時代のソフトウェア開発に対してどのような意味合いを持ってくるのかです。

ここでは文脈の変化として以下について考えます。

  • メニーコア
  • ハードウェア性能の向上
  • 分散処理
  • イベント駆動
  • Big Data / 大規模演算

メニーコア

CPUクロック数は、物理法則上の制約や発熱/電力消費量の問題から頭打ちになることは確実です。これを補うため、CPU性能の向上はコア数の増加によって行われるようになるでしょう。

例えばコア数が100のCPUが普通になった時にどのようなプログラミング・モデルが必要になるのかとイメージしてみると面白いと思います。

ハードウェア性能の向上

CPUクロック数の向上はどこかで頭打ちになるにしても、メニーコアという形でCPU性能自体は引き続き向上していきます。これに加えて、メモリ容量の増大や記憶装置のシリコン化、ネットワーク速度の向上といった要因によって、ボトルネックがI/Oからプログラムのアルゴリズム側に来るのではないかという点です。

分散処理

一般の業務アプリケーションがPaxosといったような分散アルゴリズムを直接使うようになるとは考えにくいですが、アプリケーションの処理を遂行する上で、複数の外部サービスと非同期通信しながら情報交換し、最後に複数の処理を待ち合わせて結果としてまとめる、といった処理はごく普通になるでしょう。こういった、複数の外部サービスと非同期通信しながら進める処理をここでは分散処理と呼ぶことにします。

分散処理では、複数のスレッドを駆使した並行プログラミングが必至になるのに加えて、遅延や障害への対応も重要な項目になります。

イベント駆動

従来の企業アプリケーションは、業務端末からのコマンド投入とバックエンドでのバッチ処理の組み合わせで構成されていました。これに付随する形でEDI的な連携処理を組み合わせていく形になります。これは、Webアプリケーションでも土俵がWebになっただけで、基本的には同じ枠組みになります。

しかし、スマートフォンやタブレットの普及、SNSやO2Oといったアプリケーションの興隆によってネット上を飛び交うイベント、メッセージの量は飛躍的に増えます。また、これらのイベントに対する反応の繰り返しによって事態が進行していく形のアプリケーションが増えていくことが予想されます。

つまり、アプリケーション・アーキテクチャも従来型のコマンド駆動+バッチ処理からイベント駆動に移っていくことが考えられます。

Big Data / 大規模演算

クラウド・アプリケーションの重要なポイントとなるのは、かつてのスーパーコンピューターがコモディティ化されて、一般の業務アプリケーションが日常的に使用できるようになったことです。

今まではスーパーコンピューターでのみ行われていたような大規模データ処理、大規模演算を通常業務の目的で普段使いで使うことになります。このためスーパーコンピューターで有効だった技法がどんどんコモディティ化して、業務アプリのテクニックとなっていくでしょう。

2012年11月12日月曜日

SimpleModelerとUML

story driven literate modeling with ubiquitous language model compilerでは、SimpleModelerのコンセプトを以下のように考えてみました。

  • story driven
  • literate modeling
  • ubiquitous language
  • model compiler

一点訂正があって、model compileはauto codingが最新の考え方でした。(SimpleModeler (クラウド温泉@小樽))「ドメインライブラリの自動コーディング」がSimpleModelerの守備範囲で、プログラム全体を自動生成をするものではない、という割り切りを行なっています。

さて、これを従来型のUMLと比較してみましょう。

story driven

オブジェクト指向モデルで静的構造が重要なのは論を待ちませんが、これだけだとデータ・モデリングとほとんど変わらないのでオブジェクト指向のよさが出てきません。 

そういう意味で重要なのは、シナリオ分析による動的モデルの抽出とその実現でしょう。ユースケースやロバストネス分析、ユーザー・ストーリーといった手法がこの系統の技術ですが、このあたりがオブジェクト指向の華ということができるでしょう。

シナリオ分析の特徴は自然言語で記述したシナリオを元に、オブジェクト間の相互作用を分析し、責務の割り当てを行なっていくことですが、ここで重要なのが「自然言語で記述したシナリオ」です。

しかし、この自然言語の扱いがUMLの弱点と言えます。UMLは、文字による情報を記述したり管理したりする機能があまり強くなく、UMLモデラでもコメントをプレインテキストで記述できるというような形の周辺機能という扱いになっている印象です。

このため、オブジェクト指向の「華」であるシナリオ分析との関係が薄くなってしまいます。UMLで記述できるのは、シナリオ分析が終わった後の綺麗に整形された後のモデルとなります。

literate modeling

literate modelingでは、モデルと仕様記述の自然言語が渾然一体となった成果物(artifact)が必要になりますが、UMLはグラフィカル言語であり、このようなことはできません。

ubiquitous language

ubiquitous languageでは、自然言語による仕様記述、モデル、プログラムで共通する語彙を定義し、この語彙をハブにして自然言語による仕様記述、モデル、プログラム間を連携します。

UMLは、自然言語に対する扱いが弱いので、語彙をハブにして自然言語と連携していくことは不得手といえます。

auto coding

MDAは動的モデルも含めてプログラム全体を自動生成するというアプローチだったと思います。これには、オブジェクト指向モデルで、システム全体の動的モデルをプログラム・レベルの精度で記述する必要がありますが、この点はうまくいかなかった、ということが現時点での結論と考えてよいと思います。 

前述したように、SimpleModelerでは「ドメインライブラリの自動コーディング」とターゲットを絞ることでこの問題を回避しています。

逆に考えると、UMLでもこの手法を取れば同様の効果を期待できます。

UMLの使い所

まずひとつ断っておきたいのは、オブジェクト指向に対するUMLの貢献はとても大きく、UMLなしではこのような発展はなかったということです。UMLでは、オブジェクト指向のグラフィカル言語に加えて、メタモデルの定義も行なっており、モデリングにおける共通語彙として大きな役割を担っています。

その前提の上で、システム開発作業における適材適所を考えると、グラフィカル言語としてのUMLをモデル記述言語として使うのは、あまり得策ではないのではないか、というのがボクの立てた仮説です。

その大きな理由の一つが、UMLが「自然言語」の扱いが弱いために、story driven, literate modeling, ubiquitous languageの目的に向いていないということです。

これらの目的を達成するために、SimpleModelerではEmacs org-modeをベースにしてプレインテキストのフォーマットであるSmartDoxをDSLのホスト言語として採用しました。自然言語とモデルを自然な形で混在できるので、story driven, literate modeling, ubiquitous languageを達成できるではないかと考えています。

さて、この枠組の中でもUMLの位置付けを考えてみます。

まず、オブジェクト・モデリングにおける共通語彙としてのUMLの価値は不変です。SimpleModelerでも、メタモデル(SimpleModeling)はUMLをベースにして、企業アプリ向けのプロファイルを乗せる形にしています。

また、モデルの視認性、可読性という意味ではデファクトのグラフィカル言語であるUMLを使うのが自然です。そこで、SimpleModelerではモデルからUMLのクラス図や状態機械図を自動生成するアプローチをとっています。

2012年11月9日金曜日

story driven literate modeling with ubiquitous language model compiler

SimpleModeleのコンセプトというか大枠を表現する言葉を思いついたのでメモ。

SimpleModeler = story driven + literate modeling + ubiquitous language + model compiler

図にするとこんな感じです。



モデルは大きく柔らかいモデル(soft model)と固いモデル(hard model)に分けられます。柔らかいモデルは、モデルのみに存在し、直接プログラムの自動生成の対象にはならないモデルです。固いモデルは、プログラムの自動生成の対象となるモデルです。

story drivenは、柔らかいモデルにある物語(story)を起点にモデリングをすすめることを示しています。

literate modelingは、SmartDox DSL(やMindmap DSL)によって、自然言語とモデルを混在する記述が行えることを示しています。(Literate modeling)

ubiquitous languageは、SmartDox DSL上でドメインモデルを軸に自然言語、柔らかいモデル、固いモデルでユビキタス言語を共有することを示しています。(MindmapModelingと集合知(2) - ユビキタス言語, MindmapModelingと集合知(11) - SmartDox DSLによるユビキタス言語)

model compilerは文字通り固いモデル(ドメイン・モデル)からプログラムの自動生成を行うことを示しています。

SimpleModelerは日本語による仕様記述とモデルとプログラムの融合(MindmapModelingと集合知(3) - 日本語とモデルとプログラム)が重要なテーマとなっていますが、ここにきてうまいバランスで要素技術が収斂してきました。

2012年11月8日木曜日

Scala Tips / Streamで脱出

関数型プログラミングでは、遅延評価を用いて大量データを処理するのが定番のテクニックになっています。パズルを解く問題のアルゴリズムによく出てきますね。

ただ、業務アプリケーションではパズルを解くようなロジックを書くことは稀なので、こういったテクニックの存在は知っていても、なかなか使う機会はないのではないでしょうか。

とはいえ、この手のテクニックの引き出しはできるだけ多いにこしたことはありませんし、来るべきメニーコア時代の並列プログラミングでは必須のテクニックになりそうな予感もあります。このため、機会があれば使って慣れておくのが得策ですが、これに適した普段使いできるテクニックが欲しいところです。

問題

関数fは引数のIntをそのまま返す関数です。実行の確認をするためにprintln関数で、受け取ったIntをコンソールに表示します。

val f = (x: Int) => {
  println(x)
  x
}

次に、Seq[Int]を引数に取り、関数を適用した結果が条件似合うものが見つかったら、計算前の値を返すという関数legacyを定義します。

ループ内でif式で条件判定してreturnで強制的に関数から脱出しています。returnによる強制復帰は手続き型としてはごく普通の書き方です。

def legacy(xs: Seq[Int], f: Int => Int): Option[Int] = {
  for (x <- xs) {
    if (f(x) == 3) return Some(x)
  }
  return None
}

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

scala> legacy(List(1, 2, 3, 4, 5), f)
1
2
3
res3: Option[Int] = Some(3)

関数型

Scalaで関数型的なプログラミングに慣れてくると、returnで強制脱出するようなコーディングに違和感が出てきます。そして、コンビネータを使った以下のようなコーディングを多用するようになります。

scala> List(1, 2, 3, 4, 5).map(f).find(_ == 3)
1
2
3
4
5
res33: Option[Int] = Some(3)

実行の結果無事、正しい結果が帰ってきました。コーディングも簡潔なので万々歳に思えますが、問題がひとつあります。

正しい結果を返すということに関して、List内の4, 5を関数fで評価することは不要ですが、上記の処理では評価が行われています。この例は要素数が5つしかないので実用上は問題ありませんが、1万件のデータに対して3件目で条件がヒットするにもかかわらず、残り9997件の評価が行われるようになってしまうとなると、これはちょっとした事件です。

遅延評価

ここで登場するのが遅延評価です。

ListをStreamに変えると、Steram内の要素に対してmapメソッドで関数fが適用されるタイミングが変わり、不要な要素に対する評価が行われないようになります。

scala> Stream(1, 2, 3, 4, 5).map(f).find(_ == 3)
1
2
3
res34: Option[Int] = Some(3)

関数legacyと同様の動きですね。要素1, 2, 3への評価は行われるものの、要素4, 5への評価は行われずに済みました。

Streamは、ListやVectorと同様にSeqなので使い方は難しくありません。普通にSeqとして使っていけばよいわけですが、コンビネータで値が評価されるタイミングが事前一括評価ではなく、必要時の個別評価になる点が異なります。

今回は普段使いのプログラミングでこの性質を利用するパターンを見つけました。他にも色々あるはずなので、うまくパターンとして採取していきたいと思います。

諸元

  • Scala 2.10.0-RC1

2012年11月7日水曜日

「MindmapModelingと集合知」まとめ

ちょっと間があいてしまいましたが、10月22日に名古屋で行われたクローズな集まりでのセッションのまとめです。

「MindmapModelingと集合知」から「MindmapModelingと集合知(12) ー オントロジー」までがネタ整理、「Literate modeling」は「MindmapModelingと集合知」のまとめ的な記事になっています。

今回はちょうど開発中だったSimpleModelerのSmartDox DSLの位置付け、意味などを整理して考えるよい機会になりました。キーワードとして抽出できたのが「ユビキタス言語」と「Literate modeling」です。

プログラムを作りながら考えていることは漠然としているので、そのままではすぐには言語化できません。今回のような機会があると、言語化のよいきっかけになりますね。関係者の皆さん、どうもありがとうございました。

Scala基礎勉強会

「MindmapModelingと集合知」とは直接関係はありませんが、前日に開催されたScala基礎勉強会に触発されて、以下のブログも書きました。

Scalaはなんといってもトレイトですね。

2012年11月6日火曜日

ゆるふわScalaプログラミング

11月30日に開催されるBPStudy#63で「Scalaプログラミング・マニアックス」と題してお話させていただくことになりました。

タイトルはうっかり「Scalaプログラミング・マニアックス」という怖いものにしてしまいましたが、実際は「ゆるふわScalaプログラミング」という趣旨です。

ボク自身はもちろん関数型言語の専門家というわけではなく、奥義を極めたわけでもないので、そういう意味でそもそも深いお話はできないのですが、OOPプログラマが足掛け5年Scalaプログラミングを続けてきて分かったこと、感じたことの情報共有は可能ですし、これから関数型に取り組むプログラマ、今後のプログラミングの方向性を知りたいプログラマの方には有益ではないかと思います。情報共有が目的なので"ゆるふわ"ですね。

概要には、以下のキーワードを入れました。

  • トレイト
  • モナド
  • 型クラス
  • 代数的構造
  • 代数的データ構造
  • 永続データ構造

Web系やエンタープライズ系のプログラマがあまり面識のないと思われる概念、用語が多数登場します。Scalaプログラミングをしていなければ、ボク自身もまだ聞いたこともなかったに違いありません。

こういった概念、用語の具体的な内容はハードボイルドになってしまって"ゆるふわ"ではありませんね。それより、本格的なクラウド・アプリケーションを書くために必要な新しいプログラミング・モデルの外観を共有することを目的としたいと思います。この新しいプログラミング・モデルの全体地図がどうなっていて、これらの概念がどのあたりに存在しているのかといった大きな枠組についてScalaを素材にして考えてみます。

ネタ整理

例によってセッションのネタ整理をブログで行っていく予定です。今回はおおむね既知の内容をまとめることになるので、それほど多くの記事にはならないと思います。

以前のネタ整理

以前のネタ整理の出だしとまとめです。

「MindmapModelingと集合知」はよく見たらまだまとめを作ってませんでした。「Literate modeling」がまとめに近い内容になっています。

2012年11月5日月曜日

Scala Tips / case classによるDSL

前回取り上げたNamed and Default Argumentsですが、DSLにも大きな影響があります。

ScalaのDSLというと、ScalaTestなどが提供している以下のようなDSLを思い浮かべます。

class StackSpec extends FlatSpec with ShouldMatchers {

  "A Stack" should "pop values in last-in-first-out order" in {
    val stack = new Stack[Int]
    stack.push(1)
    stack.push(2)
    stack.pop() should equal (2)
    stack.pop() should equal (1)
  }

  it should "throw NoSuchElementException if an empty stack is popped" in {
    val emptyStack = new Stack[String]
    evaluating { emptyStack.pop() } should produce [NoSuchElementException]
  }
}

こういった華麗なDSLの問題は、開発コストが結構かかるという点です。また、Scalaの物理的な文法に沿いながらも、独自の文法を編み出すということでもあるので、利用者側の学習コストも馬鹿になりません。

ScalaTest級の大物フレームワークの場合は、こういったところに力を入れても得るところが大きいですが、ちょっとしたマイ・ローカル・プログラムではなかなかこういう所にコストを掛けるのも大変です。

そこで、ボクが最近愛用しているのが、地味にcase classを使う方法です。

たとえば、こういうcase classを定義します。

case class Config(
  name: String,
  version: String = "1.0",
  drivers: Seq[Driver] = Nil)

case class Driver(
  name: String,
  url: String,
  params: Map[String, String] = Map.empty)
  Driver("google", "http://www.google.com")))

これを、こういう感じでDSLに使います。

Config("foo", drivers = List(
  Driver("yahoo", "http://www.yahoo.com"),
  Driver("google", "http://www.google.com")))

Named and Default Argumentsの機能を活用することで、不要なパラメタ設定を減らすことができるのが魅力的です。2.8以前はこういうことができなかったので、case classでDSLを作ることのメリットが限定的だったのですが、最新仕様では状況がかわっているというわけです。

細かいですが、以下のようにcase classをtoStringで文字列化した時に、データの内容が分かりやすく整形されるのは、デバッグ時にうれしい機能です。

scala> Config("foo", drivers = List(
     |   Driver("yahoo", "http://www.yahoo.com"),
     |   Driver("google", "http://www.google.com")))
res2: Config = Config(foo,1.0,List(Driver(yahoo,http://www.yahoo.com,Map()), Driver(google,http://www.google.com,Map())))

このDSLは、case classの特徴を引き継いでおり代数的データ型永続データ構造の性質を合わせ持つ不変オブジェクトでもあるので、内部データ構造としてもそのまま使うことができます。前回紹介したcopy constructorも強い味方です。

華麗なDSLの場合、内部処理用のデータ構造に情報を転記しなければならないことになりがちなので、その作業が不要になるのはかなり大きなメリットです。

諸元

  • Scala 2.10.0-RC1

2012年11月2日金曜日

Scala Tips / case classのcopy constructor

ちょっと旧聞になりますがScala 2.8(2009年)で導入されたNamed and Default Argumentsは地味ですが非常にインパクトのある機能でした。Named and Default Argumentsと同時にCompiler-generated copy methods、いわゆるcopy constructorも導入されました。

これらの機能の導入によってcase classの価値が大幅に向上したといえます。

たとえば、以下のcase class Personがあるとします。これは何の変哲もないcase classですね。

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

Personのインスタンスは以下のように生成します。

scala> val a = Person("Taro", 30, "123-456-7890")
a: Person = Person(Taro,30,123-456-7890)

さて、このPersonの年令を30から35に変更するとします。Personの実装は不変オブジェクトなので値を直接変更することはできないので、値を変更した新しいオブジェクトを再作成します。

普通に考えるとこの処理は以下のようになります。この方法の問題点は、case classの定義する変数をすべて並べて再設定しなければならないことです。この例では、変数の数が3つなのでたいしたことはありませんが、実際のプログラムでは10個ぐらい並ぶのはざらなのですし、データベースのレコードを引き写したcase classだと100個(100カラム)になるかもしれません。こうなってくるとプログラミング時に手で記述していくのは苦行になってしまいます。

scala> val b = Person(a.name, 35, a.phone)
b: Person = Person(Taro,30,123-456-7890)

そこで登場するのがcase classに自動的に追加されるcopyメソッドです。

以下のように値を変更するパラメタのみを指定してcopyメソッドを呼び出すと、指定された値が更新された新しいcase classインスタンスを得ることができます。

scala> val b = a.copy(age = 35)
b: Person = Person(Taro,35,123-456-7890)

copyメソッドを使うことで、不変オブジェクトの一部を更新する永続データ構造系のテクニックをcase classで簡単に使えるようになります。

このあたりのテクニックについて、以前書いたものを調べてみたら、今回の内容とかなり近しいものが見つかりました。視点がちょっと違うのでよしとしましょう。

諸元

  • Scala 2.10.0-RC1

2012年11月1日木曜日

Scala Tips / flatMapとbind(>>=)の違い

ScalaのflatMapメソッドは、モナドのbindに対応するメソッドとして認識されています。つまり、基本的にはScalazの>>=メソッドと同じ動作をするわけですが、微妙な機能差があります。

以下はListに対してflatMapメソッドを用いてモナドのbind処理を行ったものです。

scala> List(1, 2, 3).flatMap(x => if (x % 2 == 0) List(x, x) else Nil)
res16: List[Int] = List(2, 2)

これはScalazの>>=メソッドも全く同じ動作をします。

scala> List(1, 2, 3) >>= (x => if (x % 2 == 0) List(x, x) else Nil)
res17: List[Int] = List(2, 2)

次の例

さて、今度の例はListのコンテナに対してOptionをflatMap関数で適用しています。これも想定通りの動作をしました。

scala> List(1, 2, 3).flatMap(x => if (x % 2 == 0) Option(x) else None)
res18: List[Int] = List(2)

しかし、Scalazの>>=メソッドでは文法エラーになってしまいました。

scala> List(1, 2, 3) >>= (x => if (x % 2 == 0) Option(x) else None)
<console>:14: error: type mismatch;
 found   : Option[Int]
 required: List[?]
              List(1, 2, 3) >>= (x => if (x % 2 == 0) Option(x) else None)
                                                            ^
<console>:14: error: type mismatch;
 found   : None.type
 required: List[?]
              List(1, 2, 3) >>= (x => if (x % 2 == 0) Option(x) else None)

なぜ、このような結果になってしまうのでしょうか。

モナドは単なる自己関手の圏におけるモノイド対象だよ。」という有名な?説明がありますが、モナドは俗っぽい言い方をするとコンテナ(モナド)の中にコンテナが入っている構造で、外側のコンテナと内側のコンテナが同じ型の時に一つにまとめる事ができるものです。

ここで重要なのは、外側のコンテナと内側のコンテナが同じ型でなければならないという前提条件です。「List(1, 2, 3) >>= (x => if (x % 2 0) Option(x) else None)」が文法エラーになってしまうのは、外側のコンテナがList、内側のコンテナがOptionで、(各々はモナドではあるものの)型が違うためですね。

Scalazの>>=メソッドはモナドのbindとしては正しい動作になっているわけです。

flatten

ScalaではListなどのコレクションはflattenメソッドを提供しています。flattenメソッドは、外側のコンテナと内側のコンテナの型の相違は気にせず、以下のように内側のコンテナを平坦化する処理を行います。

scala> List(None, Some(2), None).flatten
res20: List[Int] = List(2)

これをflatMapメソッドの動作と同じになるようにしてみると以下になります。flatMapメソッドは文字通りmapした後にflattenする処理を行うわけですね。

scala> List(1, 2, 3).map(x => if (x % 2 == 0) Option(x) else None).flatten
res23: List[Int] = List(2)

flatMapメソッドは基本的にはモナドのbindと考えておいてよいですが、flatMapメソッドの適用範囲がモナドより少し広いことを知っておくとプログラミングの幅が広がります。

特に、外側のコンテナがList(といったSeq)、内側のコンテナがOptionの組合せはScalaプログラミングでは頻出なので、flatMapメソッド(あるいはflattenメソッド)でこの組合せを捌く方法はScalaプログラマの必須イディオムといえます。

諸元

  • Scala 2.10.0-RC1
  • Scalaz 2.10.0-M6

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を生成します。

現状の現状

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