読者です 読者をやめる 読者になる 読者になる

スプーキーズのちょっとTech。

SPOOKIES社内のより技工的な、専門的なブログページです。

GHCJSを使う

Haskellロゴ

先日、この記事を読む機会があり、GHCJSを使ってみたくなったので、セットアップ〜DOMを生成してaddEventListenerぐらいまでの流れを書きます。

GHCJSとは

GHCJSは、Haskellを書くとJavaScriptを吐いてくれるコンパイラです。

GHCJSのセットアップ

前提

  • OSが*nix
  • 以下のツールがインストールされていること
  • StackとHaskellを普通に使ったことがあること
    • これらの説明はしません

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.yamlextra-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-fluxghcjs-vdomなど、GHCJSで動くライブラリなども出てはいるので、将来的にもっと楽になる可能性はあると思います。 特にreact-fluxの方は、ドキュメントもちゃんと書いてあるっぽいし、試してみたいですね。

あと、生成されたJavaScriptファイルのサイズがめっちゃでかいです。60行強のHaskellコードが1MBぐらいのJavaScriptになります。

*1:Stackのghcjs用のテンプレートもあるのですが、2016-03-27現在はうまく動かないみたいです。動かし方知ってる人がいたら教えてください。