はじめに
先日、Elmを試したけどあまり合わなかった。
HTMLをプログラムで生成せずに昔ながらのやり方でファイルに書きたい。それでいて型指定も欲しい。
なのでGHCJSを試した。
GHCJSはElmのような新しい言語があるのではなく、Haskellのコンパイラ出力をJavaScriptに差し替えるというなかなか凄いやり方。Haskellの機能がほとんどそのまま使えて、更にDOMの機能も仕様の定義から自動生成されている。
自動生成なので ByteLengthQueuingStrategy のようなマイナー(?)なクラスにも対応するHaskellの関数があったりする。
つまり機能不足で困ることはほとんど無いと予想できる。
その代わりに提供されているのは素のJavaScriptのHaskell版というようなものなので、追加で miso のようなフレームワークを使った方が開発速度は速いと思う。
Haskellの環境を整える
brew install haskell-stack
StackはHaskell版brewみたいなもの。すぐにインストールできる。
取りあえず最低限のプログラムを実行してみる。
stack new haskell-sample --resolver=nightly-2018-05-10
--resolver
無しの場合は最新版LTSのコンパイラがインストールされる。取りあえずここはあとで使うのでバージョン指定で。
cd haskell-sample
stack build
.stack-work/install/x86_64-osx/nightly-2018-05-10/8.4.2/bin/haskell-sample-exe
someFuncと出れば成功。
これでHaskellの環境は終了。
Haskell版のコンパイル
続いてJavaScriptを書く環境を作る。
と言ってもサンプルプロジェクトをビルドするだけ。
https://github.com/nishimura/ghcjs-form-sample1.git
ここに置いた。
git clone https://github.com/nishimura/ghcjs-form-sample1.git
cd ghcjs-form-sample1
stack build
ghcjs-dom のコンパイルで固まったように見えるが落ち着いて待つ。メモリも大量に持って行かれるが、最初に書いたようにDOMで使う関数や型の大半をHaskell用に頑張って変換しているところなので。
ちなみに MacBook Pro で43分かかった。
コンパイルされたHaskell版プログラムを実行する。
.stack-work/install/x86_64-osx/nightly-2018-05-10/8.4.2/bin/ghcjs-form-sample1-exe
これでWebサーバーが立ち上がったので、 http://localhost:8000/ にアクセスして確認する。
これはHaskellで動いている状態。
JavaScript版のコンパイル
次はJavaScriptで動かす。
最初に~/.stack/config.yaml
にバージョン差異をスルーする設定を書く。
echo 'allow-newer: true' >> ~/.stack/config.yaml
それからGHCJSのコンパイルに使うツールをインストールする。
stack install alex
stack install happy
export PATH="$HOME/.local/bin:$PATH"
PATHはbashrc等にも追加する。
node.jsも無ければインストール。
brew install node
そしてビルド。
stack build --stack-yaml=js.yaml
GHCJS自体のコンパイルに38分かかった。それから続いてghcjs-bootでGHCJSで使うライブラリのコンパイルが始まり、追加で44分かかった。
この時点で .stack-work/install/x86_64-osx/nightly-2018-05-10/ghcjs-8.4.0.1_ghc-8.4.2.20180505/bin/ghcjs-form-sample1-exe.jsexe/
にJavaScriptが生成されている。
open .stack-work/install/x86_64-osx/nightly-2018-05-10/ghcjs-8.4.0.1_ghc-8.4.2.20180505/bin/ghcjs-form-sample1-exe.jsexe/index.html
ブラウザで確認すると、コンソールに
id="area" not found
と出ていればOK。
htmlが自動生成されたものになっているので、まだ動かない。
今回利用するHTMLファイルはhtml/index.html
にある。
JavaScriptのコンパイル(圧縮)
Closure Compilerの起動に必要なjavaのjreをここから手に入る。
https://www.java.com/ja/download/
完全にブラウザ用としてインストールされるっぽいけども気にせずこれを使う。
ln -s /Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java ~/bin/java
javaをPATHの通っているところに置いたあと、JavaScriptのコンパイルしてブラウザでアクセス。
./compile.sh
open html/index.html
これで終了。
Haskellのプログラムの説明
Main.hs
はAppMain.hs
を起動するだけ。
本体は
application :: HTMLDocument -> HTMLElement -> JSM ()
application doc area = do
Just body <- getBody doc
releaseClick <- on area G.click $ do
(x, y) <- mouseClientXY
newParagraph <- createElement doc "p"
text <- createTextNode doc $ "Click " ++ show (x, y)
_ <- appendChild newParagraph text
_ <- appendChild body newParagraph
return ()
-- Make an exit button
exitMVar <- liftIO newEmptyMVar
exit <- createElement' doc TagSpan
text <- createTextNode doc "Click here to exit"
_ <- appendChild exit text
_ <- appendChild body exit
releaseExit <- on exit G.click $ liftIO $ putMVar exitMVar ()
-- Force all all the lazy evaluation to be executed
syncPoint
-- In GHC compiled version the WebSocket connection will end when this
-- thread ends. So we will wait until the user clicks exit.
_ <- liftIO $ takeMVar exitMVar
releaseClick
releaseExit
setInnerHTML body "<h1>Ka kite ano (See you later)</h1>"
return ()
こんな感じ。
https://github.com/ghcjs/jsaddle
ここのサンプルプログラムを写したんだけど、結構書き換えないと動かなかった。インターフェースをカジュアルに変えるのはHaskellあるあるっぽい。
Helper.hs
は Element
=> HTMLElement
変換用。
型が厳密すぎて、getElementById で取ってきた Element はHTMLElement ではないので GlobalEventHandlers のonclickイベントが付けられない。つらい。
まとめ
コンパイル待ち時間を抜いたら15分ぐらい。
コンパイル待ち時間を入れたら2時間ぐらいでセットアップできた。
(実は長時間待った後の最後にエラーが出てやり直したりして5時間ぐらいかかっている)