先日、この記事を読む機会があり、GHCJSを使ってみたくなったので、セットアップ〜DOMを生成してaddEventListenerぐらいまでの流れを書きます。
GHCJSとは
GHCJSは、Haskellを書くとJavaScriptを吐いてくれるコンパイラです。
GHCJSのセットアップ
前提
GHCJSのビルド
GHCJSをビルドするためのディレクトリを作成し、stack.yamlを作成します。
$ mkdir ghcjs-build $ cd ghcjs-build $ vim stack.yaml
stack.yaml
:
flags: {} extra-package-dbs: [] packages: - '.' setup-info: ghcjs: source: ghcjs-0.2.0_ghc-7.10.3: url: https://github.com/nrolland/ghcjs/releases/download/v.0.2.0.20151230.3/ghcjs-0.2.0.20151230.3.tar.gz extra-deps: [] compiler-check: match-exact compiler: ghcjs-0.2.0_ghc-7.10.3 resolver: ghcjs-0.2.0_ghc-7.10.3
そして、GHCJSをビルドします。
$ stack build
結構時間がかかります。だいたい30分ぐらいです。
GHCJSにパスを通します。
$ export PATH=PATH:$HOME/.stack/programs/x86_64-osx/ghcjs-0.2.0_ghc-7.10.3/bin/
GHCJSで何か書く
GHCJSのセットアップ
まず、適当にプロジェクトを立ち上げます*1。
$ stack new ghcjs-test
生成されたstack.yaml
の、resolverとcompilerを、以下のように置換します。
compiler: ghcjs-0.2.0_ghc-7.10.3 resolver: ghcjs-0.2.0_ghc-7.10.3
このままstack build
しようとすると、stack setup
を先に実行しろ、と怒られるので、言うとおりにします。
$ stack setup
非常に長い時間がかかります。だいたい1時間ぐらい掛かります。
これが終わると、HaskellをJavaScriptにコンパイルすることができるようになります。
$ stack build
生成されたJavaScriptファイルは、プロジェクトディレクトリ直下の、
.stack-work/install/x86_64-osx/ghcjs-0.2.0_ghc-7.10.3/ghcjs-0.2.0.20151230.3_ghc-7.10.2/bin/ghcjs-blog-exe.jsexe/
にあります。
ghcjs-blog-exe.jsexe
以下には幾つか.jsファイルがありますが、all.js
に全てのコードが入っているようです。
これをhtmlファイル内で読み込めば、Haskellのプログラムをブラウザ上で動かすことができます。
DOM操作ライブラリ"ghcjs-dom"の導入
Haskellの標準ライブラリではDOMを操作することはできないので、GHCJS用のDOM操作ライブラリであるghcjs-domを導入します。
プロジェクトのcabalファイルに、以下のように追記します。
...省略... library hs-source-dirs: src exposed-modules: Lib build-depends: base >= 4.7 && < 5 , ghcjs-dom <== ココ default-language: Haskell2010 ...省略...
このままstack build
しようとすると、
While constructing the BuildPlan the following exceptions were encountered: -- While attempting to add dependency, Could not find package ghcjs-dom in known packages -- Failure when adding dependencies: ghcjs-dom: needed (-any), stack configuration has no specified version (latest applicable is 0.2.3.1) needed for package ghcjs-test-0.1.0.0 Recommended action: try adding the following to your extra-deps in /home/sangenya/.apps/spoo-blog/stack.yaml - ghcjs-dom-0.2.3.1 You may also want to try the 'stack solver' command
と言われます。
これを解決するには、上記エラーに書いてあるように、stack.yaml
のextra-deps
という項にghcjs-dom-0.2.3.1
を加えるか、stack solver
コマンドを叩く必要があります。
後者は便利ですが、メモリをめちゃくちゃ喰います。私の環境では2~3GBぐらい持って行かれました。
前者はその心配をしなくても良いですが、dependency treeが深いライブラリを使うときは大変です。お好きな方をお選びください。
コードの書き方
後述の「突然の死ジェネレーター」のソースを見てもらえばわかりますが、基本的にはMonadIOモナドを駆使する感じです。
私がコードを書いた時は、
- 書きたい処理をJavaScriptで考える
- →ghcjs-domのライブラリから似たような名前の関数がないか調べる(getElementByIdなど)
- →見つからなければghcjs-domのgithubレポジトリを検索する
- →型から使い方を推測してコードを書く
の流れでだいたい行けました。
作ったもの
突然の死ジェネレータを作ってみました。
実際に書いたコードは以下のとおりです。
Haskellのコードは両方合わせて60行強程度に収めることが出来ました。
GHCJSを使ったことがない人でも、HaskellとJavaScriptを触ったことがある人なら、だいたい何をやっているのか解ると思います。
感想
生JavaScriptを書いてるみたいで正直つらい。
確かに、(Haskellのサブセットとか、Haskell-likeな言語ではない)本物のHaskellでブラウザで動くものを作れるのは楽しいし、生JavaScriptを書くよりは幾分簡潔に出来てる気はしますが、やってることは手続き型言語です。 もっと規模が大きなものを作るのなら違うのかもしれませんが、正直このくらいの規模のものを作るなら、生JavaScriptを書いたほうがいいのでは、と思います(Haskell好きなら楽しいけどな!)。
とはいえ、react-fluxやghcjs-vdomなど、GHCJSで動くライブラリなども出てはいるので、将来的にもっと楽になる可能性はあると思います。 特にreact-fluxの方は、ドキュメントもちゃんと書いてあるっぽいし、試してみたいですね。
あと、生成されたJavaScriptファイルのサイズがめっちゃでかいです。60行強のHaskellコードが1MBぐらいのJavaScriptになります。
*1:Stackのghcjs用のテンプレートもあるのですが、2016-03-27現在はうまく動かないみたいです。動かし方知ってる人がいたら教えてください。