Meteorでオセロを書いてみた
Meteor 0.3.2 PREVIEW
http://meteor.com
Meteorは、高機能なWebアプリを素早く作るためのプラットフォーム。TwitterとTechCrunchで知った。以下のような特徴が印象的。
- Ruby on Railsのようにひな形が一発で生成できる。
- ただ、RoRと比べて、吐き出すファイルは少ない。基本のExampleの場合、テンプレート機能のある.html、.js、.cssの3つのみ。
- データベースにはMongoDBを使用。ドキュメント指向で、スキーマが不要。JSONでクエリーを投げることができ、取っつきやすい。
- おそらく一番の肝は、サーバーとクライアントで透過的にデータベースが扱えること。片方での変更が他方に自動的に同期される。
- Routeファイルもないらしく、基本的にすべてが単一のHTML上に読み込まれたJavaScriptで記述される。
- ただし、publicフォルダにおいたファイルは静的リソース(これらのhtmlにはテンプレートも使えない模様)として機能する。
- サーバー、クライアントのコード両方にJavaScriptを使い、サーバーではNode.jsが走っている(ただし、IOは非同期ではない)。サーバー用とクライアント用のコードを同じファイルの中に書くことも出来る。
- serverフォルダにおいたファイルはサーバー専用コード、clientフォルダに置いたファイルはクライアント専用になる。
- HTML/JS/CSSファイルを変更して保存すると、自動的にブラウザ側でリロードされる(「ホットコードプッシュ」)。
- デプロイも簡単とのこと。Tarballが作られ、Node.js(とMongoDB)が動くサーバーならすぐに走らせられるらしい。
- Meteorのサイトにデプロイするなら、meteor deploy hoge.meteor.comで簡単にデプロイできる。--passwordでパスワードもつけられる。
- デプロイ時にはすべてのCSSとJSがそれぞれminifyされて1つのファイルになってHTMLからリンクされ、クライアントに送信される。
- モジュールの追加・削除が簡単。まだライブラリは少ないものの、標準でjQuery, CofeeScript, LESSなどが用意されている。
「これまで2週間かかっていたことが数時間で出来るようになる」とのうたい文句。
公式サイトのスクリーンキャストがなかなか鮮やか。
http://meteor.com/screencast
僕もデータをリアルタイムに同期する例としてオセロを書いてみた。直感的、簡潔に書けるという印象。HTMLで盤を書くのにハマったりして5時間近くかかってしまったものの、少し慣れれば確かに生産性は高そうだ。
Othello by Meteor
http://othello.meteor.com
http://github.com/nebuta/MeteorOthello
二つのブラウザ間で黒と白を決め、交互に石を置ける。サイト上に一つしか盤面がないので2人入ったらあとは見てるだけしか出来ない。それと見ている誰かがResetを押すと即リセットしてしまうというなかなかの糞仕様なのは勘弁してほしい。
懸念としては、データベースをサーバー/クライアントで共有するためにセキュリティの問題がよく言われているようだが、近々認証などを導入する予定だとのこと。また、パフォーマンス面も、リロード時や動かしている途中にもたつく事があった(ローカルサーバーでも気づくくらいの同期のタイムラグがある)ので、若干懸念がある。それと、meteor.comのサイトがここ1日で何回も落ちているので、スケーラビリティとか大丈夫なのかな、単に今のサーバーのキャパに対してデプロイされたアプリが予想外に多すぎたのかな、とか、いろいろ心配になった。
Scalaで書いたコードをMATLABから呼ぶ
MATLABは研究でよく使われている。既存のコードも多い。ただプログラマにとっては、MATLABの文法や関数の作法が特殊で書きづらく生産性が低く感じる。数学や統計の関数はともかく、ファイル操作などのユーティリティ関数をMATLABのために新たに覚えるのが面倒。また速度にも不満がある。手動で行列要素を操作したりすると、我慢できない遅さになることがある。組み込みの行列演算は速いのだが。
というわけで、Scalaで外部プログラムを書いてMATLABと連携させてみた。かなり簡単にできた(Mac OSX Lion、Scala 2.9.1とMATLAB R2011bを使用)。
- 流れ
- Scalaでプログラムを書き、Jarファイルを作る。
- MANIFEST.MFファイルにClass-Path: scala-library.jarの1行を追加
- scala-library.jarファイル(Scalaのバイナリ配布物の/lib内にある)をJarと同じフォルダにコピー
- MATLABにてjavaaddpathコマンドで、出来たJarファイルを追加する。
- 必要ならばMATLABのJavaヒープサイズを拡大。
- Scalaでプログラムを書き、Jarファイルを作る。
Scalaプログラム作成
matlabtest.scala
package nebuta.matlablib import scala.math._ object Main { def hello: Unit = { println("Hello.") } def main(args:Array[String]): Unit = { hello } } object Filter { type Double3D = Array[Array[Array[Double]]] def numel(mat: Double3D): Int = mat.length * mat(0).length * mat(0)(0).length def matsize(mat: Double3D): (Int,Int,Int) = (mat.length, mat(0).length, mat(0)(0).length) def matsum(mat: Double3D): Double = mat.flatten.flatten.sum def matmean(mat: Double3D): Double = matsum(mat)/numel(mat) def normalize(a: Seq[Double]): Seq[Double] = { val s = a.sum a.map(_/s) } } class Complex(val re: Double, val im: Double) { def abs: Double = sqrt(re*re + im*im) def arg: Double = atan2(im,re) }
MANIFEST.MF
Manifest-Version: 1.0 Created-By: 1.6.0_29 (Apple Inc.) Main-Class: nebuta.matlablib.Main Class-Path: ./scala-library.jar
fsc matlabtest.scalaでコンパイル後フォルダ構造は以下のようになる。
$ tree . ├── MANIFEST.MF ├── nebuta │ └── matlablib │ ├── Complex.class │ ├ .... (class files) │ └── Main.class └── matlabtest.scala
Jar作成
$ jar cvfm ../ScalaTest.jar MANIFEST.MF .
これで親フォルダにScalaTest.jarができる。
scala ScalaTest.jarあるいはjava -jar ScalaTest.jarで実行してみる。
$ cd .. $ java -jar ScalaTest.jar Hello.
上手くいった。ScalaのライブラリがJarのマニフェストファイルで指定されているので、単なるjavaコマンドで実行できる。まあここまでは単なるScalaの話。
MATLABで読み込み
MATLABでScalaTest.jarのあるフォルダに移動し、以下のコマンドでJarが読み込まれる。
>> javaaddpath ScalaTest.jar
あとは普通に呼べる。
>> import nebuta.matlablib >> Main.hello Hello. >> a = Complex(1,1); >> [a.re a.im] ans = 1 1 >> a.abs ans = 1.4142 >> b = rand(3,3,3); Filter.matsum(b) ans = 12.5841
なお、Jarを更新したときは
MATLAB上で
- 使用しているScalaオブジェクトをclear
- javarmpath ScalaTest.jarで一回消し、
- 再度javaaddpath ScalaTest.jar
する必要がある。
それを自動化したのが以下のMATLABコマンド。コピペしてEnterで順に実行される。
javarmpath ScalaTest.jar cd scalatest [status,result] = system('/opt/local/share/scala-2.9.1.final/bin/fsc matlabtest.scala','-echo'); if status == 0 system('/usr/bin/jar cfm ../ScalaTest.jar MANIFEST.MF .'); cd .. javaaddpath ScalaTest.jar else cd .. disp('Compile error.') end
MATLABのsystemコマンド内ではfscやjarのフルパス(bashのwhich fscで調べる)を指定する必要がある。
ヒープサイズの拡大
デフォルトだと128MBとかですぐにオーバーしてしまう。以下を参照してMATLABの設定を変更。
http://www.mathworks.com/support/solutions/ja/data/1-B8OO49/index.html?product=ML
Macの場合はアプリケーションを右クリックしてパッケージの内容を表示し、java.optsファイルに1行、-Xm2048mなどと加える。
まとめ
いったんScalaで書く方法を確立しておけば、今後はいろいろ出来て楽そう。MATLABのデータとJavaのデータがどう変換されるのかなど、まだ分からないこともある。
ImageJ pluginの開発 by Scala on IntelliJ IDEA (Mac OSX)
ImageJのプラグインをScalaで書いている。これまでは
http://imagejdocu.tudor.lu/doku.php?id=howto:plugins:the_imagej_eclipse_howto
このページに解説されているようにしてEclipseを使っていたが、Eclipse上のScalaは、
- 自動インデントがバグっている(ストレス大)
- デバッグも、ブレークポイントの設定はできるが変数の値が表示できないので意味がない。
- リファクタリングも動かない。
といった欠点があり、しかもリファクタリング中に固まってEclipseのworkspaceファイルが壊れてしまったので、さすがにこれはキツいと思い、巷で評判のいいIntelliJ IDEAに移行することにした。上記の欠点が解決して今のところとても快適な感じ。できないと聞いていたインクリメンタルビルドも動的リロードという機能でできているようだ。手動でメニューからMakeしないといけないものの。)
移行といっても自動のインポートが上手くいかなかったので、IntelliJで新規プロジェクトを作成し、ファイルをコピーして手動で設定した。
自分への覚え書きをかねて設定法を書いておく。
IntelliJ IDEAのフリー版(community edition)をダウンロード
Scala SDKのダウンロード
sudo port install scala29でインストールできるコンパイラと実行環境とは別に、Scala SDKをダウンロードして/opt/local/の下に手動でコピーした。このSDKはscala-library.jarというscalaプログラムの開発と実行に必要な重要なファイルを含む。
Scalaプラグインのインストール
メニューからIntelliJ IDEA->Preference->Plugins->Browse repositoriesでScalaを選択。
ImageJソースのコピー
http://developer.imagej.net/idea
ここに書いてあるように、SVNのレポジトリからチェックアウトしてダウンロードすることもできるが、そうすると最新のver.2のアルファ版が取れてしまう。通常の最新版はver〜1.4で、しかもフォルダとクラス構造もver.2と1.4でだいぶ違うようなので(PlugInFrameなどのクラスが見つからなかった)、普通に以下のサイトからSourceをダウンロードして適当なフォルダに展開した。
http://rsbweb.nih.gov/ij/download.html
プロジェクト、モジュールの作成、ImageJのファイルをコピー
新規の空のプロジェクトを作成し、その中にモジュールを2つ作る。EclipseでいうworkspaceがIntelliJのプロジェクト、プロジェクトがモジュールに相当する。1つのモジュールがImageJ用、もう一つのモジュールがScalaで書くplugin本体用。それぞれにsrcフォルダーを作成する。
ImageJ用のモジュールのsrcフォルダーにImageJのソースコード、そしてsrcに並べて他のimages, macros, pluginsフォルダなどをコピーする。IntelliJのProjectペインにドラッグすれば、確認ダイアログが出た後、ファイルのコピーとモジュールへのインポートが行われる。
plugin用のモジュールの設定
- Projectペインでモジュールを選択し右クリック->Add Framework SupportでScalaを選ぶ。これでこのモジュール内でScalaソースファイルを認識できる。
- モジュール選択→右クリック→Open Module Settingsを開く。Module SDKは適当なJDKを選ぶ。そしてDependenciesのタブでImageJのモジュールとscala-library.jarを追加する(重要! これらがないとScalaソース中で赤字でシンボルが見つからないというエラーがでる。)
ImageJ用のモジュールの設定
Module SDKで適当なJDKを選択する。
Run Configurationの作成
- Run->Edit Configurationsでプラスアイコン→Applicationでconfigurationを作成し、Main classをij.ImageJ、Working directoryをImageJのフォルダに設定する。
- ここで解説されているように、コンパイルされたクラスファイルをjarにまとめてImageJフォルダにコピーするためのAntファイルを作り、モジュールのフォルダ内にコピー。
http://imagejdocu.tudor.lu/doku.php?id=howto:plugins:the_imagej_eclipse_howto
- Projectペイン内で追加したAntファイルを選択し、右クリックのメニューからモジュールに追加する。
- Run->Edit Configurationsの中でRun Ant Targetをチェックし、Antの適切なターゲット(上の解説のケースでは"main")を選択。
以上。こうやって書くと結構面倒なプロセスだったことに気づく。実際2時間くらい試行錯誤していた気がする。ただ最初に設定すればいいだけで、今後の生産性はぐっと上がるだろう。
Scalaがいい感じ
Scalaのメリット(・デメリット)
最近Java VM上で動くオブジェクト指向+関数型のScala(スカラと読む)という言語にハマっていて、ImageJのプラグインをScalaで書いたりしている。私はJava/Rubyの中級の入り口くらいの日曜プログラマだが、そのような人間にとって、Scalaはかなり取っつきやすい。
Scalaはスクリプトとしてクラス定義なしに書いて実行することもでき、またJavaのようにクラスの中にmainを定義したプログラムをコンパイルして実行することもできる。
Scalaで感じている主なメリットは、
- すべてがオブジェクトであり、まるでRubyのようにメソッドチェーンやmap,イテレータを使い、直感的で簡潔な記述ができる。
- Javaのライブラリ、既存のコードを完全にシームレスに使える。
- Javaよりもソースが短く簡潔になる。変数の型宣言が不要。初期化する際の右辺のオブジェクトから型推論してくれる。
- 変数、関数に型があるので、コンパイル時にエラーを見つけられる。
- Eclipse上で書けば、書きながら相当数のバグを実行前になくせる。
- 関数の仮引数には必ず型を書かないといけないので、それが面倒に感じることもある。
- コレクション(配列など)に対して par というメソッドを呼ぶとそれ以下の関数適用が自動で並列化される!
- 仕組みはよく分からないが、試すと確かに4コアのCPUをフル活用して速度が2倍くらいになったりする。詳細は未検討。
- そのほかにもActorというメッセージングベースの洗練された並列化機構があるそうだ。これが"SCAlable LAnguageたるゆえんの一つ。
実用上感じているデメリットは今のところ、
- Eclipseできちんとオートインデントできない
- デバッガはブレークポイントこそつけられるが、変数の値は見えない。
- つまりprintfデバッグしなくてはならない。
言語の根本とは関係ないのだけど、案外ストレスがたまる。それと、
- 字句規則が若干複雑(演算子は両側に隙間を空けて書いた方が安全, etc)
- コンパイルエラーのメッセージが難解すぎて意味不明なときがある(特に型関係のエラー)
なんていうのも結構最初のうちは戸惑うところかもしれない。
まあいずれにせよ、RubyとJavaを両方知っている人には学習コストはとても低いのでおすすめです。以前Javaで書いた小さいプログラムをScalaで書き直したらその時点ですでに、ある程度使えている感があった(たぶんその取っつきやすさがScalaが最近ここまで流行ってきている理由かと思われる)。
Java/Ruby経験者のためのScala入門へのリンク(随時追加予定)
私はEclipseにScalaプラグインを入れて使っている。IntelliJ IDEAというIDEのほうが良いという意見もあるが、Eclipseの方がJavaで使い慣れていたのと、インクリメンタルコンパイルができるといったメリットがある。
- http://ofps.oreilly.com/titles/9780596155957/
- Scala入門書 フリーで全文読める(英語) まだ読破していないが丁寧で分かりやすい(若干冗長)。
- http://www.ne.jp/asahi/hishidama/home/tech/scala/index.html
- 言語仕様をJavaと比較しながら項目別に説明。よくまとまっていて便利。
辞書データを小さく出来ないか?
1次元のみ
連続した2バイトの相関ではなく、単にバイトの値の頻度分布を見る、という考え方。頻度分布を見ると、差が結構あるように見え、このアイデアは良さそうに思える。
元記事を読んでしばらく考えていたときに思いついた。2バイト相関よりも先に思いつくべきアイデアかもしれない。結果は以下の通り。
2dto1d.rb
encode_test_1d.rb
平均化
次に、2バイト相関のまま、辞書データのマップの隣り合うピクセルを平均化してみて結果を比較してみた。
makecoursevectors.rb
左から順に、EUC, UTF-8, JIS, SHIFT JIS。上の4つが元のデータ、下の4つが8x8ピクセルで平均化した例。
こうやって、粗視化してもなおエンコードごとの見た目の違いがあるので期待できた。
結果は以下の通り。(encode_test_coarse.rb)
32x32pixelの粗視化のケースでは、辞書サイズが元の1000分の1で、失敗率が3%。1次元の時よりは若干良いという感じだろうか。
余談: ブラウザ上にこの機能を実装?
この機能をGreasemonkeyで実装したいなと思ったのだけど、loadイベントでmetaタグを追加 or 変更しても文字コード解釈の変更は効かなかった。
Chromeでは、スクリプトの冒頭で@run-at document-startというオプションを指定すると早く実行できるとのこと(まだheadすら存在しない状態)だが、この段階で何かすれば上手くいくかも。
まとめ
psi氏の元サイトでは20バイトでほぼ100%の成功率に飽和しているのに対して、私の実装では100バイトまで読んで99%なので、私のは辞書データもあまり良くないのかもしれない。青空文庫の文章は記号や数字はほとんど含まないし、カタカナも少ないと思われる。Wikipediaのほうが良さそう。
ちなみに、判定に失敗しているのは英文のサイトが多い。この文章を書いていて気がついたのだけど、私の実装では、判別関数の答えの選択肢の中にASCIIが入っていない。ASCIIをASCIIとして捕らえられれば成功率が一気に上がりそう。元サイトはそうしているのかな。それと、UTF-8の辞書データの見た目が結構元サイトと違う。私の辞書データが青空文庫から取ってきているので、記号やアルファベットがほとんど無く、カタカナも少ないことが影響しているかも。
異なるエンコード間での辞書の「形」の違いを保ったまま、特徴を上手く抽出してサイズを小さく出来れば良いのだけど。結局それは画像認識みたいな世界になってしまい、どんどんややこしくなってしまうのかもしれない。
追記:高次元行列の次元を、内積をあまり変えずに下げる、ランダムプロジェクションという方法があるそうだ。ただこれは判定対象の文字列にたいしても次元縮小の行列計算が必要なのでその行列を保持する必要がある。なので辞書サイズは小さくならないかな...。