2010年6月24日木曜日

ボクらのScala

明日6月25日に拙著のScala入門書『ボクらのScala』が出版されます。


副題は「次世代Java徹底入門」となっています。
題と副題は編集のYさんがつけたものですが、なかなかよい感触です。
少し冒険的な副題ですが、二年ほどScalaをいろいろと触ってみてのボクの実感とも一致しています。
内容をみてYさんもそのように感じられたのだと思います。

Scalaは色々な言語のよいところを絶妙のバランスで配合しており、プログラミング効率も高く、高い実用性を備えています。その上で、DSL、マルチコア、クラウドといったこれからの応用を実現するために必要な機能が整備されているのが美点です。
今すぐ便利であるのはもちろん、これからの10年を乗り切るための基盤ともなる言語といえるでしょう。

ただ、言語仕様やライブラリの使い方を一度にすべて把握しようとすると初学者には敷居が高いので、普通のプログラミングをする上で普通に必要な言語仕様やライブラリを、学習段階に合わせて説明していくというアプローチを取りました。

400ページに収めるために、大幅に原稿を削りました。
原稿を書く側からすると辛いところですが、逆に読者の側からは内容がコンパクトになって読みやすくなったのではないかと思います。
割愛した原稿は、このブログで随時アップしていこうと思っています。

2010年5月4日火曜日

Scala DSL

クラウド時代に向けて:

  1. SimpleModel:オブジェクト・メタモデル。クラウド・アプリケーション向けの拡張中。
  2. SimpleModeler:SimpleModelのモデルコンパイラ。
  3. g3:メッセージフロー・フレームワーク。

の開発を行っているわけだけれど、これらの技術のキーテクノロジとなるのがDSL(Domain Specific Language)である。

SimpleModelはメタモデルだけれど、Scala DSLで簡潔に記述できることが要件の一つになっている。そして、SimpleModelのモデルコンパイラがSimpleModeler。SimpleModelerはSimpleModelを記述したScala DSLの集まりをソフトウェアリポジトリとして使用する。

またg3は、Scala DSLで記述したメッセージフロー・モデルに基づいて動作する。

このように、このところはScala DSLを中心に活動しているのだけれど、ここにいたるまで色々と紆余曲折があった。

1998年に登場したXMLがJavaと分散環境のミッシングリンクを埋めるキーテクノロジーであると感じ、Java&XMLを中心に活動していた時期がある。

分散環境では、分散ノード間でのオブジェクトの共有や移送は理論的には不可能ではないにしても相当ハードルが高く、当面は実運用的には不可能と考えてよい、というのが分散OSや分散オブジェクトの教訓。

このため、分散アプリケーションが取るべき現実解は、異機種間での疎結合のサービスを構造化された値の送受信で連携させるということになるのではないか。そしてこの「異機種間で送受信する構造化された値」を記述する技術が当時のJavaには欠けており、ここにXMLはすっぽりとはまるのではないか。

そういったブレークスルーの予感もあって、XMLアプリケーションであるSmartDoc(1998)とRelaxer(2000)を開発したのだけれど、これらのアプリケーションを通して、いわゆる「one source multi use」の有効性を体感することができた。

当時はまだ、DSL(Domain Specific Language)という用語はなかったか、あるいは一般的ではなかったのだけれど、用途ごとの専用言語をXML上に構築して「one source multi use」の運用を行うこと、つまり今の言い方ではDSLが今後の技術の主流になると思えた。

ただ、XMLをDSLのホスト言語として使用することを試行錯誤する中で、XMLは文書をマークアップするにはとても良いのだけれど、定義ファイルやプログラムといった用途に使用するのには向いていないことが分かってきた。

そこで、プログラミング言語をホスト言語にすることを考えたのが次の段階。最初に候補となったのはGroovy。これは2004年頃。Javaベースであり、メタな操作も可能なのでDSLのホスト言語としてうってつけであると思ったわけである。

しかし、いくつか問題があってGroovyを採用するのは断念した。

当時のGroovyは、非常に遅かったのが一点。ただ、こういった問題は時間が解決するので、DSLの用途ではそれほど大きな問題ではない。

木構造を扱う抽象構文的な拡張の柔軟性が低い、という問題もあった。これは、他に良いところがあれば我慢できる範囲。(なにぶん古い話なので今は違った状況になっていると思う。)

しかし、これは大きな問題だと思ったのは、動的言語であるためにDSLの構文にコンパイラ相当のエラーチェックをかけることができない、ということ。これはDSLを使ってモデルを作成していく際の効率を著しく低下させる。

たとえば、フレームワークの設定ファイルに識別子のミススペルがあることが原因で、フレームワークが意味不明のエラーで落ちる、というような事が起きる。

DSL処理系の開発も難しくなる。DSL処理系をクイックハックで作る分には楽でよいのだけれど、本格的なものにしようとするとコンパイラ相当のエラーチェックを自分で実装しなければならなくなる。

そんな中で、アノテーション技術が登場したこともあり、JavaをDSLのホスト言語とすることに決めて開発を進めていた。

しかし、JavaをDSLのホスト言語として使用した場合、メタな情報はjava.lang.reflectionとアノテーションで部分的に取り出すことができるだけなので用途が限られるという問題がある。Javaの文法を構文木として取得するのが非常に難しい。EclipseのASTを使うという案もあったのだけれど、Eclipseに依存してしまうので断念した。

以上のようなこともあり、Javaでは木構造のデータ構造を簡潔に記述するための文法を定義するのが難しい。Javaで書いたロジックをそのまま取り出すことも難しい。

もちろん、GWTのような荒業もあるけれど、これはハードルが高すぎて一般的な技術には成り得ない。

また、Javaではテキスト情報をDSLの一部として記述するのが一苦労。コメントに記述したものを、自作パーサで読み込むといったようなことをやったりしていた。

そして最後に行きついたのがScalaということになる。これが、2008年の7月。

Scalaは、高階関数と字句上の様々なトリックを併用することで、簡潔で強力なDSLを簡単に構築することができる。また、生文字リテラルやXMLリテラルがあるのでテキスト情報の記述も簡単。ケースクラスや抽出子によるリテラル追加機能もよい。しかも、型推論のついた静的型付け言語。

まさに、DSLのために生まれてきた言語である。

たとえば、以前にご紹介したg3フレームワークが使用する以下のScala DSL。Enterprise Integration PatternsのSplitパターンとAggregateパターンを使用したメッセージフローを記述している。これをXMLやJavaで記述しようとすると、大変な事になりそうだ。実装もややこしくなる。

Split.scala
class Split extends G3Application {
  start(List(1, 2, 3, 4, 5)) split() agent {
    case x: Int => x + 100
  } aggregate()
}

しかし、Scalaだととてもすんなりいくのである。実装も、簡単というわけではないけれど、XMLやJavaをDSLにした時のことを考えるとはるかに容易になる。

いずれにしても、こういったDSLの構築技術が、クラウドに限らずこれからのソフトウェア開発のキーテクノロジーになると思っている。よほどの本格的な用途でない限り、ホスト言語の上に構築する内部DSLを使うことになるので、内部DSLが最重要技術ということである。

内部DSLのホスト言語としては、現在のところRuby、Groovy、Scalaが有力だと思われるけれど、やっぱりDSLは静的型付けがよいかな、ということでボクの選択はScalaということになる。もちろん、これは各自の好みで決めてよいところ。DSL処理系の開発効率も重要なので、プログラミングに慣れている言語を選択するのがよいでしょう。

Scala DSLアプリケーションであるSimpleModelerやg3の開発を通して、二年近くScala DSLプログラミングしてきて、多少ノウハウも蓄積されてきた。そのようなこともあり、Scala DSLに関するノウハウを5月18日に行われるJJUG CCCで『Scala DSLの作り方』というセッションで発表させていただけることになった。

興味のある方はぜひどうぞ。

2010年5月3日月曜日

クラウド・モデリング

5月14日に行われる『Hadoopを中心とした分散環境での開発方法論・モデリング・設計手法等についての座談会』という座談会に参加させていただくことになった。

よい機会なので、クラウドをターゲットにして取り組んできたモデリング手法、ツール、フレームワークなどをつらつらと整理しているところ。

オブジェクト指向モデリングは、本来、振舞いモデルを記述する能力が高い所が特徴であり強みでもあるはずだけれど、今のところ有効活用されているとは言い難い。

伝統的な基幹システムも、新興のWebシステムも基本的には画面とデータベース間の転記が基本アプリケーション・アーキテクチャとなっていることが多く、この場合には従来型のデータモデリングで十分だったということかもしれない。

三段(3 tier)構成のシステムアーキテクチャといった設計レベルでのモデリングもあるけれど、アーキテクチャレベルの鳥瞰図があれば、あとは直接プログラミングした方が話が早い。

しかし、クラウドでは故障、遅延、規模といった問題が従前より重要な課題となって浮上するため、今までのようにはいかなくなるだろう。

故障、遅延、規模の問題をデータベースシステムを中心としたミドルウェア層で吸収することはもはやできなくなるわけで、性能と一貫性のバランスを取るために、アプリケーションの用途に応じて、アプリケーション側で調整を行う必要が出てくる。いずれこういった問題を吸収するクラウド・ミドルウェアが出てくることになるとは思うけど、これは10年スパンで考えていくことである。

利用者側への見せ方、エクスペリエンスにも影響が出てくる。たとえば、今までだったらフォームの完了ボタンが完了したらデータベースには確実に書かれているはずだったけど、クラウドでは後で書いときます、という約束が行われるだけとなることが多くなる。そうなると、利用者側のアクションとして、書かれたことの確認や、実は書き損なっていたのでリカバリを利用者自身にお願いするというようなユースケースが普通に出てくる。

こういった問題を取り扱うには並列、分散、非同期といった要因を上位のモデリング段階で扱わなくてはならなくなるはず。そのモデリングのためのメカニズムとしてオブジェクト指向が再評価されることになるだろう、というのが最初のアイデア。そして、システム構築という観点から見るとMQ的なキューを中心としたシステムアーキテクチャ、アプリケーションアーキテクチャになるだろう、というのが二つ目のアイデア。

当時考えていたこれらのアイデアを昨年の1月にクラウド研究会でお話をさせていただいた流れから、UNIXマガジン誌に寄稿した記事を『クラウドの技術』の一編として再録していただいた。

この2つのアイデアを、従来から考えているモデリング手法に取り込む方針で作業を進めている。

従来システムに対するオブジェクト指向のモデリング手法はSimpleModeling、MindmapModelingという形でまとめ、一昨年の夏に出版させていただいた。

SimpleModelingとMindmapModelingは、内部的には共通のメタモデルSimpleModelを使用しており、相互運用可能である。MindmapModelingでラフなドメインモデルやユースケースモデルを抽出し、SimpleModelingで本格的なモデリングに入っていくという流れを想定している。

SimpleModelでは、イベントを軸に静的モデルと動的モデルを相互連携させる点がポイントとなっている。SVOがモデリングの基本になる。Vがイベント。イベントがアクター(S)とResource(O)の状態遷移を束ねる。ユースケースも物語をイベントの列として記述することで、静的モデルと動的モデルの両方にシームレスに連結できる。

クラウド・アプリケーションの場合でも、この枠組は引き続き有効ではないか、というのが今のところの考え。とはいえ、静的モデル、動的モデルの双方に相応の拡張が必要になる。

静的モデルでは、いわゆるBASEトランザクションに対応したデータモデリングが必要になってくる。ここでSVOの枠組みを自然に拡張していくことで対応できるのではないか、と考えている。具体的には、関連の各種プロパティの解釈の精度を上げることと、イベント・エンティティにBASE向けの属性を取り入れるといったことである。

動的モデルでは、新しくメッセージ・フローという概念の導入がミッシングリンクを埋める鍵になるのではないかと考えている。オブジェクト指向に限らず、コントロール・フローやデーター・フローをモデル化する図は色々と用意されているのだけれど、コントロール・フローとデータ・フローを包含して記述するための図というのが案外ない。しかし、クラウド・アプリケーションのモデリングでは、コントロール・フローとデータ・フローを同じ文脈上で記述することが、有効なのではないか仮説を立てているところである。

この仮説に基づいて、メッセージフロー図の文法、メッセージフローのDSL、そしてメッセージフローの実行系としてのフレームワークを開発しているのが現在のステータス。メッセージフローのDSLとフレームワークが一段落したら、一昨年から昨年にかけて開発したScala DSLコンパイラSimpleModelerを、メッセージフロー対応する構想となっている。

先は長そうだけど、ぼちぼちやっていくつもり。

2010年4月10日土曜日

(仮称)クラウド研究会@札幌

4月4日に「第1回(仮称)クラウド研究会@札幌」で、以下の3つのテーマでお話をさせていただきました。幹事の中村さん(@nakayoshix)には大変お世話になりました。どうもありがとうございました。
  • Android + Mule ESB + Twitter + OpenX + Scala + EC2
  • SimpleModeler + AppEngine
  • g3フレームワーク
昨年の秋ぐらいに、SimpleModelerのAppEngine/JDO周り、静的モデル周りが一段落。まだまだ完成というわけではないんだけど、バランス的にそろそろ動的モデルを攻めてみようということで、ここ半年ほどはクラウド・アプリケーションにおける動的モデルの位置付けと実現方法について検討と試作を行ってきた。
11月にはPDC09に参加する機会があり、Windows Azureについて学ぶことができたのも大きな収穫だった。
システムの動的モデルでは、エンティティの状態遷移モデルがひとつの基本になるけど、システム全体の挙動をモデル化する手法は確立されていない。ユースケース・モデルとシステム実装を繋ぐ部分は依然として手作業になる。
もちろん、手作業といってもモデリング側も色々なモデルを用意しているし、システム側もJavaEEやSpringといったアプリケーション・フレームワークがかなりの仕事をしてくれるので、相当手間を省けるようにはなっている。
しかし、クラウド時代に入ってモデリング側の道具立ても、システム側のアプリケーション・フレームワークもゼロベースで再構築しなければならない状況になっているのは明らか。クラウド時代には非同期、並列、分散、故障、遅延を正面から取り扱わないといけない。アプリケーション・モデルではこれらの要因を捨象して、実装時に頑張るというわけにはいかないだろう。
そういった時代認識もあり、次世代のコンピューティング環境、その土台となるモデル体系とシステム基盤、アプリケーション・フレームワークについて、微力ながら色々と考えた結果到達した結論が「メッセージ・フロー」である。
メッセージ・フローを軸にしてユースケースからエンティティをつなぐ動的モデルの体系を構築し、できるだけインピーダンスミスマッチが発生しない形でDSLで記述しフレームワーク上で動作させる方法について、朧気ながら形がみえてきたような気がしており、その実現のためのDSL、DSLコンパイラ、フレームワークの開発が最近のボクのテーマになっている。
まだまだ、仕掛り中の状態なんだけど、今回はよい機会をいただいたので中間報告という形で発表させていただいた。

Android + Mule ESB + Twitter + OpenX + Scala + EC2

「Android + Mule ESB + Twitter + OpenX + Scala + EC2」ではボクが試行アプリケーションとして作成したMule ESBベースのアプリケーションを素材にメッセージ・フロー・アーキテクチャによるアプリケーションの考え方などを説明した。
ポイントとなるのは、以下のメッセージ・フロー図。コントロール・フローやデータ・フローを記述するための図はあるけど、メッセージ・フローを記述するための図が案外ないので、ボクのニーズに合わせて文法を定義した。詳細はいずれこのブログでも紹介しようと思っている。



Visioで作った図なんだけど、OpenOfficeにはない矢印を使っているためOpenOfficeでは描けないことが最近判明した。OpenOfficeでかける記法に修正する予定である。

SimpleModeler + AppEngine

「SimpleModeler + AppEngine」は、現在開発中のScalaベースのDSLモデルコンパイラSimpleModelerの紹介。
現在のところエンティティ・モデルからUMLクラス図とAppEngine/Javaのプログラムを生成できる。
動的モデルは、状態遷移図をUMLステートマシン図と状態遷移表に変換するところまで。
1月22日に開催されたAppEngine Ja Night #4で発表させてもらった時の資料「クラウド・アプリケーション:DSL駆動アプローチ:SimpleModelerの試み」を使って説明を行った。AppEngine Ja Night #4では、時間的な問題で半分まで行かなかったのだけど、今回は最後まで通して説明することができた。

g3フレームワーク

「g3フレームワーク」は、現在開発中のメッセージ・フロー・ベースのアプリケーション・フレームワークである。専用のScala DSLで記述したアプリケーションを直接実行する。
メッセージ・フローの実行基盤としてはApache CamelのScala DSLに期待していたんだけど、内部構造がやや弱いような印象もあり、進捗もはかばかしくないようにみえる。また、Apach CamelそのものもAppEngineで動かすのは無理があるような感じでもあるので、Apache Camelの利用は断念して独自にDSLとフレームワークを開発することにした。
g3フレームワークとDSLは、以下の3つの用途をカバーすることを念頭に動作セマンティクスとDSLを設計している。
  • Google AppEngine Java上で動作する
  • Mule ESBなどのESB上で動作する
  • シェルからコマンドとして実行できる
今の所、コマンドとして実行するという機能範囲を実装している。
ある程度形がついたところでAppEngine上に載せる予定。こちらは、TaskQueueを用いて直接実行させることを想定している。
並行してMule ESBへの対応も行いたいと考えている。こちらはDSLからXMLの定義ファイルを生成する形を想定している。Mule ESBを用いてEC2とWindows Azure上で動作させることができるようになることを期待している。
最終的には、DSLで記述したモデルから、用途に応じてAppEngine, EC2, Windows Azure向けのプログラムを生成し、適材適所でAppEngine, EC2, Windows Azureを選択できるようにするのが目標である。AppEngine, EC2, Windows Azureの混在環境でも有効に機能するはず。
説明/デモで用いたDSLは以下のもの。
リスト1[TwitterScan3.scala](ユーザ名、パスワードのところは改変)はTwitterのTLをAtomFeedで読んできてg3フレームワークのAtomFeedオブジェクトとして受け取るというもの。Scalaのcase sequence(PartialFunction)を使っているのがポイント。この構造にすることでリアクティブなアプリケーションを簡単に記述することができる。
TwitterScan3.scala
package org.goldenport.g3.app

import org.goldenport.g3._
import org.goldenport.g3.atom._

class TwitterScan3 extends G3Application {
service('demogon, "http://[user]:[passward]@twitter.com/statuses/user_timeline/demogon.atom")

  start invoke("demogon") agent {
    case AtomFeed(feed) => feed.toString
    case _ => "???"
  } agent {
    x => println("==> " + x)
    x
  }
}
リスト2[Split4.scala]は、Enterprise Integration PatternsのSplitterとAggregatorを記述したもの。現在の実装はSplitterがListの各要素ごとに分割したものを送信して、Aggregatorが集約してListに再構築する。Splitterは関数でカスタマイズできるようにする予定。
Split4.scala
package org.goldenport.g3.app

import org.goldenport.g3._

class Split4 extends G3Application {
  agent('compute) {
    case x: Int => x + 100
  } aggregate()

  start(List(1, 2, 3, 4, 5)) split() publish("compute")
}

参考:デモ結果

参考のために(長くなるので最後に付録的に)デモの実行結果を載せておきます。スナップショットが表示されているだけなので、中身を知らないと意味が分かりませんが参考ということで。
以下、TwitterScan3とSplit4のデモの様子。TwitterScan3は当日はデモが失敗したので初公開。TwitterScan3のデモは表示が長いのでSplit4、TwitterScan3の順番に並べている。
Split4デモ結果
> run org.goldenport.g3.app.Split4
[info] 
[info] == compile ==
[info] Source analysis: 1 new/modified, 13 indirectly \
    invalidated, 0 removed.
[info] Compiling main sources...
[warn] there were unchecked warnings; re-run with -unchecked for \
    details
[warn] one warning found
[info] Compilation successful.
[info]   Post-analysis: 257 classes.
[info] == compile ==
[info] 
[info] == copy-resources ==
[info] == copy-resources ==
[info] 
[info] == run ==
[info] Running org.goldenport.g3.Main \
    org.goldenport.g3.app.Split4
G3Pipe.do_Process1(class \
    org.goldenport.g3.G3StartNode):List(WrappedArray(org.goldenport.g3.app.Split4)) \
G3StartNode.do_Process: \
    WrappedArray(org.goldenport.g3.app.Split4)
G3Publish.do_Process
G3Context.send
lookup = [compute], Some(org.goldenport.g3.G3AgentNode@1d01844a)
G3Pipe.do_Process1(class org.goldenport.g3.G3AgentNode):List(1)
G3Pipe.do_Process2(class org.goldenport.g3.G3AgentNode):1/List()
G3AgentNode.do_Process: 1
r = 101
terminal = List(1)
G3Publish.do_Process
G3Context.send
lookup = [compute], Some(org.goldenport.g3.G3AgentNode@1d01844a)
G3Pipe.do_Process1(class org.goldenport.g3.G3AgentNode):List(2)
G3Pipe.do_Process2(class org.goldenport.g3.G3AgentNode):2/List()
G3AgentNode.do_Process: 2
r = 102
terminal = List(2)
G3Publish.do_Process
G3Context.send
lookup = [compute], Some(org.goldenport.g3.G3AgentNode@1d01844a)
G3Pipe.do_Process1(class org.goldenport.g3.G3AgentNode):List(3)
G3Pipe.do_Process2(class org.goldenport.g3.G3AgentNode):3/List()
G3AgentNode.do_Process: 3
r = 103
terminal = List(3)
G3Publish.do_Process
G3Context.send
lookup = [compute], Some(org.goldenport.g3.G3AgentNode@1d01844a)
G3Pipe.do_Process1(class org.goldenport.g3.G3AgentNode):List(4)
G3Pipe.do_Process2(class org.goldenport.g3.G3AgentNode):4/List()
G3AgentNode.do_Process: 4
r = 104
terminal = List(4)
G3Publish.do_Process
G3Context.send
lookup = [compute], Some(org.goldenport.g3.G3AgentNode@1d01844a)
G3Pipe.do_Process1(class org.goldenport.g3.G3AgentNode):List(5)
G3Pipe.do_Process2(class org.goldenport.g3.G3AgentNode):5/List()
G3AgentNode.do_Process: 5
r = 105
terminal = List(List(101, 102, 103, 104, 105))
terminal = List(5)
[info] == run ==
[success] Successful.
[info] 
[info] Total time: 2 s, completed 2010/04/10 6:49:13
TwitterScan3結果
> run org.goldenport.g3.app.TwitterScan3
[info] 
[info] == copy-resources ==
[info] == copy-resources ==
[info] 
[info] == compile ==
[info] Source analysis: 0 new/modified, 0 indirectly invalidated, \
    0 removed.
[info] Compiling main sources...
[info] Nothing to compile.
[info]   Post-analysis: 257 classes.
[info] == compile ==
[info] 
[info] == run ==
[info] Running org.goldenport.g3.Main \
    org.goldenport.g3.app.TwitterScan3
G3Pipe.do_Process1(class \
    org.goldenport.g3.G3StartNode):List(WrappedArray(org.goldenport.g3.app.TwitterScan3)) \
G3Pipe.do_Process2(class \
    org.goldenport.g3.G3StartNode):WrappedArray(org.goldenport.g3.app.TwitterScan3)/List() \
G3StartNode.do_Process: \
    WrappedArray(org.goldenport.g3.app.TwitterScan3)
G3Invoke.do_Process
G3Context.invoke
lookup = [demogon], \
    Some(org.goldenport.g3.G3ServiceNode@53015c53)
G3Pipe.do_Process1(class org.goldenport.g3.G3ServiceNode):List()
G3Pipe.do_Process2(class \
    org.goldenport.g3.G3ServiceNode):List()/List()
G3ServiceNode.do_Process: List()
2010/04/10 6:44:21 \
    org.apache.http.client.protocol.ResponseProcessCookies \
    processCookies
?x??: Invalid cookie header: "Set-Cookie: guest_id=1270849461120; \
    path=/; expires=Sun, 09 May 2010 21:44:21 GMT". Unable to \
    parse expires attribute: Sun, 09 May 2010 21:44:21 GMT
terminal = List(<?xml version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" \
    xmlns:georss="http://www.georss.org/georss" \
    xmlns="http://www.w3.org/2005/Atom">
  <title>Twitter / demogon</title>
  <id>tag:twitter.com,2007:Status</id>
<link type="text/html" href="http://twitter.com/demogon" \
    rel="alternate"/>
<link type="application/atom+xml" \
    href="http://twitter.com/statuses/user_timeline/demogon.atom" \
    rel="self"/>
  <updated>2010-04-09T21:44:21+00:00</updated>
<subtitle>Twitter updates from \
    &#20986;&#33538;&#27177;&#22826;&#37070; / \
    demogon.</subtitle>
    <entry>
<title>demogon: \
    &#33258;&#30001;&#12364;&#19992;&#12395;&#12388;&#12356;&#12383;&#12290;&#12524;&#12473;&#12488;&#12521;&#12531;&#12364;&#12356;&#12356;&#12290;</title> \
<content type="html">demogon: \
    &#33258;&#30001;&#12364;&#19992;&#12395;&#12388;&#12356;&#12383;&#12290;&#12524;&#12473;&#12488;&#12521;&#12531;&#12364;&#12356;&#12356;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554547226</id>
      <published>2010-02-24T01:56:05+00:00</published>
      <updated>2010-02-24T01:56:05+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554547226" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
    <entry>
<title>demogon: \
    &#12420;&#12387;&#12401;&#12426;&#33258;&#30001;&#12364;&#19992;&#12398;&#12452;&#12479;&#12522;&#12450;&#12531;&#12394;&#12435;&#12363;&#12393;&#12358;&#12290;</title> \
<content type="html">demogon: \
    &#12420;&#12387;&#12401;&#12426;&#33258;&#30001;&#12364;&#19992;&#12398;&#12452;&#12479;&#12522;&#12450;&#12531;&#12394;&#12435;&#12363;&#12393;&#12358;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554480122</id>
      <published>2010-02-24T01:54:33+00:00</published>
      <updated>2010-02-24T01:54:33+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554480122" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
    <entry>
<title>demogon: \
    &#27494;&#34101;&#23567;&#26441;&#12395;&#30528;&#12356;&#12383;&#12290;&#19968;&#26479;&#12420;&#12429;&#12358;&#12290;</title> \
<content type="html">demogon: \
    &#27494;&#34101;&#23567;&#26441;&#12395;&#30528;&#12356;&#12383;&#12290;&#19968;&#26479;&#12420;&#12429;&#12358;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554430782</id>
      <published>2010-02-24T01:53:26+00:00</published>
      <updated>2010-02-24T01:53:26+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554430782" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
</feed>
)
G3Pipe.do_Process1(class org.goldenport.g3.G3Agent):List(<?xml \
    version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" \
    xmlns:georss="http://www.georss.org/georss" \
    xmlns="http://www.w3.org/2005/Atom">
  <title>Twitter / demogon</title>
  <id>tag:twitter.com,2007:Status</id>
<link type="text/html" href="http://twitter.com/demogon" \
    rel="alternate"/>
<link type="application/atom+xml" \
    href="http://twitter.com/statuses/user_timeline/demogon.atom" \
    rel="self"/>
  <updated>2010-04-09T21:44:21+00:00</updated>
<subtitle>Twitter updates from \
    &#20986;&#33538;&#27177;&#22826;&#37070; / \
    demogon.</subtitle>
    <entry>
<title>demogon: \
    &#33258;&#30001;&#12364;&#19992;&#12395;&#12388;&#12356;&#12383;&#12290;&#12524;&#12473;&#12488;&#12521;&#12531;&#12364;&#12356;&#12356;&#12290;</title> \
<content type="html">demogon: \
    &#33258;&#30001;&#12364;&#19992;&#12395;&#12388;&#12356;&#12383;&#12290;&#12524;&#12473;&#12488;&#12521;&#12531;&#12364;&#12356;&#12356;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554547226</id>
      <published>2010-02-24T01:56:05+00:00</published>
      <updated>2010-02-24T01:56:05+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554547226" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
    <entry>
<title>demogon: \
    &#12420;&#12387;&#12401;&#12426;&#33258;&#30001;&#12364;&#19992;&#12398;&#12452;&#12479;&#12522;&#12450;&#12531;&#12394;&#12435;&#12363;&#12393;&#12358;&#12290;</title> \
<content type="html">demogon: \
    &#12420;&#12387;&#12401;&#12426;&#33258;&#30001;&#12364;&#19992;&#12398;&#12452;&#12479;&#12522;&#12450;&#12531;&#12394;&#12435;&#12363;&#12393;&#12358;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554480122</id>
      <published>2010-02-24T01:54:33+00:00</published>
      <updated>2010-02-24T01:54:33+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554480122" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
    <entry>
<title>demogon: \
    &#27494;&#34101;&#23567;&#26441;&#12395;&#30528;&#12356;&#12383;&#12290;&#19968;&#26479;&#12420;&#12429;&#12358;&#12290;</title> \
<content type="html">demogon: \
    &#27494;&#34101;&#23567;&#26441;&#12395;&#30528;&#12356;&#12383;&#12290;&#19968;&#26479;&#12420;&#12429;&#12358;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554430782</id>
      <published>2010-02-24T01:53:26+00:00</published>
      <updated>2010-02-24T01:53:26+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554430782" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
</feed>
)
G3Pipe.do_Process2(class org.goldenport.g3.G3Agent):<?xml \
    version="1.0" encoding="UTF-8"?>
<feed xml:lang="en-US" \
    xmlns:georss="http://www.georss.org/georss" \
    xmlns="http://www.w3.org/2005/Atom">
  <title>Twitter / demogon</title>
  <id>tag:twitter.com,2007:Status</id>
<link type="text/html" href="http://twitter.com/demogon" \
    rel="alternate"/>
<link type="application/atom+xml" \
    href="http://twitter.com/statuses/user_timeline/demogon.atom" \
    rel="self"/>
  <updated>2010-04-09T21:44:21+00:00</updated>
<subtitle>Twitter updates from \
    &#20986;&#33538;&#27177;&#22826;&#37070; / \
    demogon.</subtitle>
    <entry>
<title>demogon: \
    &#33258;&#30001;&#12364;&#19992;&#12395;&#12388;&#12356;&#12383;&#12290;&#12524;&#12473;&#12488;&#12521;&#12531;&#12364;&#12356;&#12356;&#12290;</title> \
<content type="html">demogon: \
    &#33258;&#30001;&#12364;&#19992;&#12395;&#12388;&#12356;&#12383;&#12290;&#12524;&#12473;&#12488;&#12521;&#12531;&#12364;&#12356;&#12356;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554547226</id>
      <published>2010-02-24T01:56:05+00:00</published>
      <updated>2010-02-24T01:56:05+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554547226" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
    <entry>
<title>demogon: \
    &#12420;&#12387;&#12401;&#12426;&#33258;&#30001;&#12364;&#19992;&#12398;&#12452;&#12479;&#12522;&#12450;&#12531;&#12394;&#12435;&#12363;&#12393;&#12358;&#12290;</title> \
<content type="html">demogon: \
    &#12420;&#12387;&#12401;&#12426;&#33258;&#30001;&#12364;&#19992;&#12398;&#12452;&#12479;&#12522;&#12450;&#12531;&#12394;&#12435;&#12363;&#12393;&#12358;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554480122</id>
      <published>2010-02-24T01:54:33+00:00</published>
      <updated>2010-02-24T01:54:33+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554480122" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
    <entry>
<title>demogon: \
    &#27494;&#34101;&#23567;&#26441;&#12395;&#30528;&#12356;&#12383;&#12290;&#19968;&#26479;&#12420;&#12429;&#12358;&#12290;</title> \
<content type="html">demogon: \
    &#27494;&#34101;&#23567;&#26441;&#12395;&#30528;&#12356;&#12383;&#12290;&#19968;&#26479;&#12420;&#12429;&#12358;&#12290;</content> \
<id>tag:twitter.com,2007:http://twitter.com/demogon/statuses/9554430782</id>
      <published>2010-02-24T01:53:26+00:00</published>
      <updated>2010-02-24T01:53:26+00:00</updated>
<link type="text/html" \
    href="http://twitter.com/demogon/statuses/9554430782" \
    rel="alternate"/>
<link type="image/png" \
    href="http://s.twimg.com/a/1270842741/images/default_profile_5_normal.png" \
    rel="image"/>
      <author>
        <name>&#20986;&#33538;&#27177;&#22826;&#37070;</name>
      </author>
    </entry>
</feed>
/List()
content = List()
content = List(demogon: ???R???u???????B???X?g???????????B)
xxx = demogon: ???R???u???????B???X?g???????????B
AtomContent = List(demogon: ???R???u???????B???X?g???????????B)
content = List(demogon: ???????R???u??C?^???A????????B)
xxx = demogon: ???????R???u??C?^???A????????B
AtomContent = List(demogon: ???????R???u??C?^???A????????B)
content = List(demogon: ????????????????B??t????B)
xxx = demogon: ????????????????B??t????B
AtomContent = List(demogon: ????????????????B??t????B)
G3Pipe.do_Process1(class \
    org.goldenport.g3.G3Agent):List(<feed><id>tag:twitter.com,2007:Status</id><title>Twitter \
    / demogon</title><updated>Sat Apr 10 06:44:21 JST \
    2010</updated><entry><title>demogon: \
    ???R???u???????B???X?g???????????B</title><link/><link/><content>demogon: \
 \
    ???R???u???????B???X?g???????????B</content></entry><entry><title>demogon: \
 \
    ???????R???u??C?^???A????????B</title><link/><link/><content>demogon: \
 \
    ???????R???u??C?^???A????????B</content></entry><entry><title>demogon: \
    ????????????????B??t????B</title><link/><link/><content>demogon: \
    ????????????????B??t????B</content></entry></feed>)
G3Pipe.do_Process2(class \
    org.goldenport.g3.G3Agent):<feed><id>tag:twitter.com,2007:Status</id><title>Twitter \
    / demogon</title><updated>Sat Apr 10 06:44:21 JST \
    2010</updated><entry><title>demogon: \
    ???R???u???????B???X?g???????????B</title><link/><link/><content>demogon: \
 \
    ???R???u???????B???X?g???????????B</content></entry><entry><title>demogon: \
 \
    ???????R???u??C?^???A????????B</title><link/><link/><content>demogon: \
 \
    ???????R???u??C?^???A????????B</content></entry><entry><title>demogon: \
    ????????????????B??t????B</title><link/><link/><content>demogon: \
    ????????????????B??t????B</content></entry></feed>/List()
==> <feed><id>tag:twitter.com,2007:Status</id><title>Twitter / \
    demogon</title><updated>Sat Apr 10 06:44:21 JST \
    2010</updated><entry><title>demogon: \
    ???R???u???????B???X?g???????????B</title><link/><link/><content>demogon: \
 \
    ???R???u???????B???X?g???????????B</content></entry><entry><title>demogon: \
 \
    ???????R???u??C?^???A????????B</title><link/><link/><content>demogon: \
 \
    ???????R???u??C?^???A????????B</content></entry><entry><title>demogon: \
    ????????????????B??t????B</title><link/><link/><content>demogon: \
    ????????????????B??t????B</content></entry></feed>
terminal = \
    List(<feed><id>tag:twitter.com,2007:Status</id><title>Twitter \
    / demogon</title><updated>Sat Apr 10 06:44:21 JST \
    2010</updated><entry><title>demogon: \
    ???R???u???????B???X?g???????????B</title><link/><link/><content>demogon: \
 \
    ???R???u???????B???X?g???????????B</content></entry><entry><title>demogon: \
 \
    ???????R???u??C?^???A????????B</title><link/><link/><content>demogon: \
 \
    ???????R???u??C?^???A????????B</content></entry><entry><title>demogon: \
    ????????????????B??t????B</title><link/><link/><content>demogon: \
    ????????????????B??t????B</content></entry></feed>)
[info] == run ==
[success] Successful.
[info] 
[info] Total time: 2 s, completed 2010/04/10 6:44:22

2010年3月26日金曜日

By-name Parameters

先々週に、メインで使っていたThinkPadがとうとう壊れてしまった。ファンの故障なので修理できないこともなさそうだったけど、5年程使っているマシンでディスクも50GBと少なく、メモリ2GBは今となってはEclipse&Scalaの組み合わせには到底足りないので、観念して新しいものを買うことにした。

最近、周りの人のMac率が非常に高いこともあり、スペックが魅力的だったのでiMacにしてみた。到着したのが先週の金曜。ちょうど一週間経ったけど、やっと作業環境が整備できてきた。(そういえば、ブログもこの記事がiMacからの初投稿になる。)

CPUはCore i7。4コアで、Hyper Threading機能により擬似的に8コアになる。今後、マルチコアを前提としたプログラミングモデルに移行することが予想されるので、検証の基盤として期待している。

ということで、8コアの振舞いを見たいのと、ScalaのActorを試したいということもあり簡単な並列ソートプログラムを作って動かしてみた。このプログラムについてはいずれ取り上げるかもしれないけど、今回は並列ソートプログラムの性能測定用に作った以下の小さなプログラムがテーマ。

Tmr.scala
object Tmr {
  def run(proc: => Unit) {
    System.gc
    val start = System.currentTimeMillis
    proc
    val end = System.currentTimeMillis
    println("elapse(ms) = " + (end - start))
  }
}

このプログラムはScalaの特徴的な機能であるBy-name Parameterを使っている。By-name Parameterは関数リテラル/クロージャと字句的なトリックを使って、メタな機構を導入せずに擬似的な制御構文の拡張を可能にした、まさにスケーラブルを目指すプログラミング言語Scalaの影の主役ともいうべき重要な機能である。大事なのはrunメソッドのパラメタ「proc: => Unit」の所。通常の関数パラメタだとパラメタがない場合「proc: () => Unit」となるところを、パラメタリストを省略している。この省略記法をScalaではBy-name parameterと呼んでいる。

Tmr.scalaは小さなプログラムなので、ひと目で何をしているか分かると思う。

でもできることはすごい。

基本的な使い方は次のようになる。この場合は小さなソートが5msで終了している。

scala> Tmr.run(List(2, 1, 3).sort(_ < _))
elapse = 5

何となく、見逃してしまうかもしれないけど、関数リテラルや高階関数を持たないJavaなどプログラミング言語ではこういう書き方はできない。なぜなら、Tmr.runメソッドに制御が移る前に、引数の式が評価されてしまうから。地味だけど、関数型言語ならではの機能なのである。

試しに1000msスリープする式を引数にTmr.runメソッドを実行してみると経過時間が1003秒となった。妥当なところである。

scala> Tmr.run(Thread.sleep(1000))
elapse = 1003

ここまでの機能だけだと「影の主役」はちょっと大げさ。本当に感心するのは、以下のように引数として制御構造のボディのような複数の文の列を書くことができることである。このことによって擬似的な制御構文が追加可能になっている。

scala> Tmr.run {
     |   val list = List(2, 1, 3)
     |   list.sort(_ < _)
     | }
elapse = 2

この記法を支えているのは2つの特例。まず、パラメタが1つのパラメタリストは「(」「)」だけでなく、「{」「}」が使えるという特例がある。また、先ほど説明したようにBy-name Parameterはパラメタがない関数リテラルでパラメタリストを省略することができるという特例である。この2つの特例が字句上の工夫で、組み合わせて使用すると擬似的に制御構文を追加することができる。

こういった字句上の工夫というかトリックが色々と入っているのがScalaの特徴で、一筋縄ではいかないところである。プログラミング言語は、こういった字句上の工夫が使い勝手に大きく影響するということが、Scalaを使ってみるとよくわかる。

2010年3月16日火曜日

Android IO Utility

最近Androidを触る機会があり、いろいろと試してみている。

本当はAndroidもScalaで作りたいんだけど、色々と問題があるみたいなので今のところはあきらめてJavaで書いている。とはいえ、Eclipseのコード補完、リファクタリングはやはり便利で、フレームワークにプラグインするためのハンドラを書くことが中心のプログラムではScala+Emacsよりも便利である。Androidもこの範疇に入る。

Androidを触って感じたのは、全く組込みや制御系といった気がしないとうことである。体感的には普通のUNIX&Java&GUI&Webクライアントアプリを作っている手触り。

画面が小さいのとネットワークが遅いのとメモリが少ないのとキー入力がやりにくいのとGPSなどのデバイスがたくさんついているのが違うところ。でも、プログラミングモデルは普通のJavaでよい。昔の制御系みたいな職人芸は要らないから、アイデアしだいというところが、Androidがエンジニアに受けている理由のひとつだろう。もちろん、本格的なアプリケーションを作る段になれば、色々な技術が必要になるけれど、間口が広いというのはよいことである。

Androidは、通常ファイルの読み書きができるからAppEngineのプログラミングモデルと比べるとずいぶん楽である。ファイルをBLOB/CLOB的な使い方もできるし、JSONを使ってちょっとした情報を保存しておくこともできる。

モバイルアプリケーションなので、pause/resumeの状態遷移が発生した時の状態引継ぎが必須の作業になる。さらに、アプリケーションが強制終了されても、前回のセッションの状態に復元できることが望ましいので、その意味でもアプリケーションの状態保存のために手軽に使えるデータの保存場所が必要になる。この目的にJSONと通常ファイルの組み合わせが便利なのである。

また、ActivityやServiceといったエージェント間や同一エージェント内でも異なったスレッド間ではIPC的な通信になる。こういったエージェント間での情報受け渡しや情報文脈共有に不揮発性一時データを使う場合、RDBMSを使うのが本格的だけど、JSONをファイルに格納して受け渡すのが手軽で使いやすい。

もちろん、サーバーとの通信はJSONベースでよい。

以上の点から、AndroidではJSONによるデータの取り回しを前提にしたアーキテクチャにするのが筋がよさそうと感じたわけである。org.json.JSONObjectが基本で提供されているのも大きい。

JavaでXMLを使うのは少し煩雑だし、性能的にもいい所はないので、特別な要件がなければあまりXMLにこだわらないのがよいだろう。

そんなこともあり、AndroidIOUtilityというファイル入出力のユーティリティクラスを作った。

AndroidIOUtility.java
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;

import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;

public class AndroidIOUtility {
public static JSONObject loadJsonFromFile(String filename, Context context) \
    throws IOException, JSONException {
        return new JSONObject(loadStringFromFile(filename, context));
    }

public static void saveJsonFromFile(String filename, JSONObject json, \
    Context context) throws IOException, JSONException {
        OutputStream out = null;
        try {
            out = context.openFileOutput(filename, Context.MODE_PRIVATE);
OutputStreamWriter writer = new OutputStreamWriter(out, "utf-8");
            writer.append(json.toString(2));
            writer.flush();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {}
            }
        }
    }

public static String loadStringFromFile(String filename, Context context) \
    throws IOException {
        InputStream in = null;
        try {
            in = context.openFileInput(filename);
            InputStreamReader reader = new InputStreamReader(in, "utf-8");
            StringBuilder builder = new StringBuilder();
            char[] buf = new char[4096];
            int size;
            while ((size = reader.read(buf)) != -1) {
                builder.append(buf, 0, size);
            }
            return builder.toString();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {}
            }
        }
    }

public static void saveStringToFile(String string, String filename, Context \
    context) throws IOException {
        OutputStream out = null;
        Writer writer = null;
        try {
            out = context.openFileOutput(filename, Context.MODE_PRIVATE);
            writer = new OutputStreamWriter(out, "utf-8");
            writer.append(string);
            writer.flush();
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                } else if (out != null) {
                    out.close();
                }
            } catch (IOException ee) {}
        }
    }

public static void saveInputStreamToFile(InputStream in, String filename, \
    Context context) throws IOException {
        OutputStream out = null;
        try {
            out = context.openFileOutput(filename, Context.MODE_PRIVATE);
            byte[] buf = new byte[8192];
            int size;
            while ((size = in.read(buf)) != -1) {
                out.write(buf, 0, size);
            }
            out.flush();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException ee) {}
        }
    }

public static void saveResourceToFile(int resourceId, String filename, \
    Context context) throws IOException {
        InputStream in = null;
        try {
            in = context.getResources().openRawResource(resourceId);
            saveInputStreamToFile(in, filename, context);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException ee) {}
        }
    }

public static void truncateFile(String filename, Context context) throws \
    IOException {
        OutputStream out = null;
        try {
            out = context.openFileOutput(filename, Context.MODE_PRIVATE);
            out.write(new byte[0]); // XXX needs check
            out.flush();
        } finally {
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {}
            }
        }        
    }
}

内容は簡単なファイル入出力なので難しいところはない。それより、Android向けのアプリケーション・アーキテクチャを意識した機能セットという意味で見てもらえると参考になるかもしれない。

1つはJSONの入出力を基本に考えていること。理由は前述したとおり。

また、画像なども通常ファイルとしてキャッシュしたり受け渡ししたりすることになるので、生バイナリデータの入出力も必要になる。

saveResourceToFileメソッドは、リソースに格納した画像データをServiceへの受け渡し目的でファイルに書き出す必要があったので作った。リソースからのデータの取り出しも色々なパターンがありそうなので、目的に応じて用意するとよいだろう。

文字コードはUTF-8決め打ちにしている。もちろん、他の文字コードの読み書きが必要なケースも出てくるだろうけど、自アプリケーション内での通信や不揮発性一時データの保存に用途を絞ればUTF-8決め打ちが簡潔である。

アプリケーションデータはSDCardに置いておくのが作法のようなので、それむけのメソッドもいずれ作ることになると思う。ファイル入出力周りはGoogleのGuavaが便利そうなので、これを使うように改造してもよいかもしれない。

2010年3月10日水曜日

Scalaのロジック、Javaのロジック

OptionとListとforとflatMapではPersonのaddressメソッドの実装として、以下の2つを挙げた。Optionに対して、for文&パターンマッチングやflatMapメソッドを使うのがScala的なプログラミング。 いずれにしても一度ListにしてからList操作の関数を活用するのがポイントである。

Person.scala
class Person(val name: String) {
  var zip: Option[String] = None
  var prefecture: Option[String] = None
  var city: Option[String] = None

  def address: String = {
    (for (Some(s) <- List(zip, prefecture, city)) yield s).mkString(" ")
  }
}
PersonFlatMap.scala
class Person(val name: String) {
  var zip: Option[String] = None
  var prefecture: Option[String] = None
  var city: Option[String] = None

  def address: String = {
    List(zip, prefecture, city).flatMap(s => s).mkString(" ")
  }
}

Personのインスタンス変数zip, prefecture, cityを「List(zip, prefecture, city)」としてList化して、Listとして統一的に処理できるようにしている。そして、このListをfor文のジェネレータに指定したり、ListのflatMapメソッドを直接使ったりして、List操作を行うわけである。

for文では、「Some(s)」によるパターンマッチング、yieldによるListの生成もがすこぶる便利。

また「OptionとListとforとflatMap」で述べた通りOptionとflatMapの組合せは魔法的な便利さがある。

もう一つ、面白いのがmkStringメソッド。Listの各要素を引数で指定された区切り記号を挟んで連結したStringを生成するメソッドである。文字列処理では何かとこの処理が出てくるので、専用メソッドが基本クラスに定義されているのはとても便利である。分かってらっしゃる、という感じ。

さて、Scala的なプログラミングがいかに効率がよいかという点を確認するためにJava的なプログラミングでaddressメソッドを実装してみよう。ボクの場合は、以下のようにプログラムすることになる。

Person.java
public class Person {
    final String name;
    String zip = null;
    String prefecture = null;
    String city = null;

    public Person(String name) {
        this.name = name;
    }

    public String address() {
        StringBuilder buf = new StringBuilder();
        boolean first = true;
        if (zip != null) {
            first = false;
            buf.append(zip);
        }
        if (prefecture != null) {
            if (first) {
                first = false;
            } else {
                buf.append(" ");
            }
            buf.append(prefecture);
        }
        if (city != null) {
            if (first) {
                first = false;
            } else {
                buf.append(" ");
            }
            first = false;
            buf.append(city);
        }
        return buf.toString();
    }
}

    

Scalaでリライトするとこんな感じになる。

PersonNotList.scala
class Person(val name: String) {
  var zip: String = null
  var prefecture: String = null
  var city: String = null

  def address: String = {
    val buf = new StringBuilder
    var first = true
    if (zip != null) {
      first = false
      buf.append(zip)
    }
    if (prefecture != null) {
      if (first) {
        first = false
      } else {
        buf.append(" ")
      }
      buf.append(prefecture)
    }
    if (city != null) {
      if (first) {
        first = false
      } else {
        buf.append(" ")
      }
      first = false
      buf.append(city)
    }
    buf.toString
  }
}

なんとも長いプログラムになるけど、Javaで普通に書くとこうなってしまう。zip, prefecture, cityというインスタンス変数ごとにほとんど同じだけど少しずつ違うロジックを並べていくことになるのが美しくない。Stringを段階的に構築していくことになるためStringBuilderを導入することになり、その取り回しも煩雑である。

また「Listの各要素を引数で指定された区切り記号を挟んだStringを生成する」というmkStringメソッドの処理は、実は案外煩雑である。これを実現するためにfirstというフラグを使って区切り記号を挿入するタイミングを制御したり、StringBuilderの操作も必要になる。

以上の比較から分かる通り、Listというデータ構造を軸にして事前に用意されているアルゴリズムを適用していく関数型言語的なプログラミングモデルははまると非常に強力である。もちろん、ある特殊な場合のみ便利でそれ以外はそうでもないとか、逆に不便ということであれば価値は低いわけだけど、Scalaの場合は、はまる場所が頻出するので、結果としてプログラミング効率が大幅に向上する。

また、データ構造のアルゴリズム的な処理を簡潔に記述できたとしても、字句上の見た目が煩雑では効果半減である。この点もScalaは高いレベルでニーズを満たしている。

このあたりの違いが分かってくると、Javaでのプログラミングはなんとももどかしくなってくる。「Scalaはオブジェクト指向と関数型のハイブリッド」と聞いても「だから?」という感じだけれど、実際にコーディングしてみると関数型言語とのハイブリッドの効果がよく分かる。宣言的に書ける、副作用がないように書ける、といった理念的な部分も重要だけど、実務的には簡単に短く書けるということがより重要である。「簡単に短く書ける」理由をよくよく見てみると、Listというデータ構造と高階関数の組合せによるものであり、Lisp時代から不変の方程式である。この方程式を現代的なAlgol系の静的型付オブジェクト指向プログラミング言語の文法に違和感なく溶け込ますことができたのがScalaの美点といえる。