Haste + Parsec でブラウザ上で動く電卓をつくる

11 Dec 2024:  Haste 公式サイトのリンクがドメイン失効ののち他者に取得されていたため，github に差し替え．最近は webassembly がアツいのかしら．

# 序

[Haste](https://github.com/valderman/haste-compiler) は haskell で書いたら javascript にコンパイルできて超ハッピーというアレです．

* 関連: [Haskell + Haste で Fourier 級数の可視化](http://qiita.com/lesguillemets/items/dbd928237be3e6a1221d)

Haste のいいところの1つは，haskell で書かれたライブラリも使いうる[^libs]というところです．Haskell ですごく便利なライブラリで，ブラウザ上で動かしたいかもしれないものといえば [Parsec](https://hackage.haskell.org/package/parsec) を思いついたので，Haste と Parsec を組み合わせてブラウザ上で動く電卓を作ってみましょう．

# 準備

## パーザを書く

まずは後で haste を使うことは忘れて，普通に haskell で Parsec を使って電卓に使えるパーザを書きましょう．今回は最終的に `Double` の計算をすることとします． `expr :: Parsec String u Double` という型のものができればオッケーです（名前は何でもいいですが，以下 `expr` はこの函数を指します）．ここは Haste に関係ないので省略しますが，[Haskell 構文解析 超入門](http://qiita.com/7shi/items/b8c741e78a96ea2c10fe) という記事が非常にお勧めです[^1]．

Parsec に不慣れなのでだいぶ不安なのですが，以前上の記事を読んだ記憶を頼りにとりあえず加減乗除と括弧に対応したものを書きました．

```hs:Parse.hs
module Parse (expr) where
import Text.Parsec

-- |
-- >>> parseTest expr "1 + 2*(3+5)"
-- 17.0
expr :: Parsec String u Double
...
--- このあといい感じの実装
```

## Haste 側の準備

ghc とは別に，Haste が Parsec を呼べるようにしておく必要があります．最近の Haste だと `haste-cabal` というツール[^2]が同梱されており，これを使ってこのへんの管理をします．以前 haskell 環境を作りなおして以来若干大胆なので，`sandbox` とか使わずにインストールしてしまいましょう．

```bash:terminal
$ haste-cabal install parsec
```

Haste 側の準備はこれだけ（！）です．次で完成します．

# 書き書き

実装はどうしましょうか．シンプルな電卓ということ，折角 parsec を使うこと，などを考えてこういうふうにしてみます：

1. `<input type="text" .. >` に計算したい式を打ち込む
2. `keyUp` で計算を試みる．成功したらそれを表示，失敗したら parsec のエラーを表示．
3. 表示は `div.textContent` で[^3]．

html を用意します．

```html:index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Calc</title>
  <script src="./build/calc.js"></script>
</head>
<body>
  <div>
    <input type="text" name="calcInput" id="calcInput">
  </div>
  <div id="output"></div>
</body>
</html>
```

あとは，「`keyUp` で `#calcInput` の中身を呼んで，さっき書いたパーザに渡した結果を `#output` に書き込む」という部分を，`Haste` を `import` したうえで haskell で書いてやればおしまいです．

```haskell:Calc.hs
import Haste
import Haste.DOM
import Haste.Events
import Text.Parsec

-- さっき書いたパーザ
import Parse (expr)

main = do
    -- elemById ::  MonadIO m => ElemID -> m (Maybe Elem)
    Just input <- elemById "calcInput"
    Just output <- elemById "output"
    -- onEvent element event (EventData evt -> m ()) という形
    -- この場合 e にはイベント情報が入り，（今回は使っていないが）函数に渡される
    onEvent input KeyUp $ \ e -> do
        -- getValue :: (IsElem e, MonadIO m, JSType a) => e -> m (Maybe a)
        Just ln <- getValue input
        case parse expr "" ln of
             Right s -> setProp output "textContent" (show s)
             Left l -> setProp output "textContent" (show l)
```

コンパイルすれば終わり


```bash:terminal
$ hastec ./src/Calc.hs -isrc -o ./build/calc.js
$ firefox index.html
```

結果は[こちらです<small>(github.io)</small>](http://lesguillemets.github.io/haste-parsec-example/)．`keyUp` で動くのでちょっと動きが面白い．

# まとめ

Haste と Parsec の組み合わせで簡単にブラウザ上で動く電卓を作ることができました．この辺りのライブラリを使えることで加速するうれしさというものもあると思いますし，場合によっては昔 ghc でコンパイルするために書いたソースを流用することもできましょう．積極的に使っていきましょう．


[^1]: それに限らず，この方の入門記事は全般に大変為になるので，いくつか目を通すといいと思います．
[^2]: 名前の通り `cabal` からの fork ですが，普通に haste をインストールするときは `haste-boot` で降ってきます．
[^3]: 最初 [`innerText`](http://caniuse.com/#search=innerText) つかってて，firefox でエラーも吐かずに動かなくてちょっと悩みました．
[^libs]: 残念ながらTemplate haskell にはまだ対応していないので，それを使っているライブラリ(lens など．cf: [オートマトンで遊ぶやつを作った - Just $ A sandbox](http://myuon-myon.hatenablog.com/entry/2015/12/18/193654)) は使えません．また hashable も現時点では[build できない](https://github.com/valderman/haste-compiler/issues/51)ようです．