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