ClojureScript でボイド(Boids)なプログラムを書いてみる
はじめに
ClojureScript の勉強のために自分で書いてみたいコードを探していたところ、以下の記事を発見。
【ボイド】JavaScriptとHTML5で『群れ』をシミュレーションしてみよう【プログラミング】 - あのねノート。
JavaScript → ClojureScript に書き直してみることにしました。
作るもの
ボイド(Boids)という人工生命シュミレーションプログラムを作ります。詳しくは元記事を参考にしてください。
完成品はこんな感じとなります。ソースはこちら
プログラミングの下準備
ClojureScript で作成するにあたり、leiningen と lein-cljsbuild を活用します。
- Leiningen: Clojure(ClojureScriptを含む)のプロジェクトを便利に管理するためのツール。
- lein-cljsbuild: Leiningen のプラグイン。 これを使うことにより ClojureScript のコンパイルを自動で行える。
現状、ClojureScript で何かを作るならこの二つはデファクトなんで何も考えずに利用しましょう。
プロジェクト作成
lein new cljs-boids
Leiningen の lein
コマンドでプロジェクトのひな形を作成します。
project.clj へ記述を追加
(defproject boids "0.1.0-SNAPSHOT" :description "Sample program of Boids written by ClojureScript" :url "https://github.com/snufkon/clj-boids" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :source-paths ["src/cljs"] :dependencies [[org.clojure/clojure "1.5.1"] [org.clojure/clojurescript "0.0-2069"]] :plugins [[lein-cljsbuild "1.0.0"]] :cljsbuild {:builds [{:source-paths ["src/cljs"] :compiler {:output-to "resources/public/js/main.js" :optimizations :advanced}}]})
:dependencies
にclojurescript
を追加。:plugins
にlein-cljsbuild
を追加。:cljsbuild
を追加。
コンパイルオプション
ちょっとだけ補足しておきますが、ボイドのプログラム作成には関係ないので読まなくても良いです。
コンパイルのオプションに:advanced
を指定していますが、開発中はデバッグのため
:optimizations :whitespace :pretty-print true
を指定しています。このオプション指定だとコンパイル後に出力されるmain.js
のコードが読める形で出力されます。
:advanced
オプションだと、強力な最適化を行って、コードサイズや実行にかかる時間を少なくしてくれますが、出力されたコードは読めません(頑張れば読めるのかも...)。ちなみに、以下のようなことをしてくれているみたいです。
- 変数や関数の名前を短いものにリネームする(mungingと呼ばれる)。
- オブジェクトのネストを平板化する。
- 未使用のコードを消去する。
- インライン関数を作成する。
- JavaScriptランタイムの特性に沿ってパフォーマンスを最適化する。
以下の本より引用。
- 作者: Stuart Sierra,Luke Vanderhart
- 出版社/メーカー: Oreilly & Associates Inc
- 発売日: 2012/11/07
- メディア: ペーパーバック
- この商品を含むブログを見る
:advanced
でコンパイルする際、いくつかの前提を元にコードを処理するため、処理対象のソースコードがこの前提に沿っていないと出力されたコードが期待する動作とならない場合もあることを頭に入れておきましょう。開発の途中途中、たまに:advanced
でコンパイル&実行して動作確認しておくのが良いと思います。プログラム完成後、:advanced
でコンパイル&実行、動かない...とならないように気をつけましょう(自戒を込めて...)。
index.html の作成
元記事のindex.html
をそのまま、resources/public/index.html
にコピーでおしまい。
main.cljs の作成
コード全体は、こちらで確認してください。気になる部分だけ解説しておきます。
(def boids (array)) (defn make-boids [] (loop [i 0] (when (< i NUM_BOIDS) (aset boids i (js-obj "x" (* (js/Math.random) SCREEN_SIZE) "y" (* (js/Math.random) SCREEN_SIZE) "vx" 0 "vy" 0)) (recur (inc i))))) (make-boids)
boids
はarray
とjs-obj
で表現します。はじめ、Vector
とMap
で表現してプログラムを作ったんですが、元のJavaScriptのコードと比較してあまりにも遅かったため書き直しました。(StackOverflowで質問したらarrayを使えと)
array
のデータへの取得、更新にはaget
,aset
をそれぞれ利用します。
(aget boids index "x") ;; boids[index].x (aset boids index 10) ;; boids[index] = 10
(set! (.-onload js/window) init)
set!
を使い、window.onload
にinit
関数を設定します。
(js* "debugger;")
js*
を利用してJavaScriptのコードを直接埋め込むことができます。完成したプログラムには記載していませんが、デバッグ時に上記のコードを活用していました。
(.time js/console "label name") ;; 計測したい処理を記述 (.timeEnd js/console "label name")
こちらも完成したプログラムに記載していませんが、処理時間を計測するために上記コードを活用していました。
おわりに
ClojureScript でプログラムを書くのに慣れていないので思ったより時間がかかりました。最初にboids
を Vector&Map で表現して作成するまでは良かったんですが、実行速度に問題があることに気づき、処理時間を計測したり、boids
の表現をarray
に変えたり、完成して:advanced
でコンパイルしたら動かなかったりなんなりで時間を浪費しました。最終的に完成したコードを見て思ったのは、「JavaScriptで書けばいいんじゃないかと?」、これは自分のClojureScriptのスキルが足りないのが原因な気もします。今回は ClojureScript の勉強だったので良いのですが、この程度のプログラムならそのまま JavaScript で書いたほうが良いのかなと思いました。