Haste で atcoder に挑戦してみｒ…ん？

11 Dec 2024: 公式サイトへのリンクが切れていてドメインが他者のものに変わっていたので，github と hackage へのリンクに差し替えました．

# 序

[Haste-compiler](https://github.com/valderman/haste-compiler) は Haskell から javascript へのコンパイラで，javascript との連携も（js -> haste ([`Haste.Foreign.ffi`](https://hackage.haskell.org/package/haste-compiler-0.5.0/docs/Haste-Foreign.html#v:ffi))も， haste -> js  ([`Haste.Foreign.export`](https://hackage.haskell.org/package/haste-compiler-0.5.0/docs/Haste-Foreign.html#v:export))も）考えられています．

[atcoder](http://atcoder.jp/) ではプログラミングコンテストが定期的に行われており，javascript (node.js) など様々な言語で解答を提出することができます．

……ﾃｨﾝ!!!

# ということで
たとえば [Atcoder Beginner Contest 030 - A](http://abc030.contest.atcoder.jp/tasks/abc030_a) を解いてみます．

## stdin を読む

まず node.js でどのように出入力をするのか見てみましょう．

twitter での AtCode の公式アカウントより

<blockquote class="twitter-tweet" lang="en"><p lang="ja" dir="ltr">AtCoderで使える言語としてJavaScript(node.js)を追加しました！（入出力は <a href="http://t.co/Ukh28vk7">http://t.co/Ukh28vk7</a> を参考にしていただけると良いと思います！</p>&mdash; AtCoder (@atcoder) <a href="https://twitter.com/atcoder/status/212110872368267265">June 11, 2012</a></blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>

リンク先を参照するとこういう感じでやるのがいいみたいです:

```js:input_sample.js

// input :: String, 入力全体を受け取って解く
function Main(input) { ... }

// 入力を読み込んで Main に渡す
Main(require("fs").readFileSync("/dev/stdin", "utf8"));
```

というわけで，（全部 Haste でも書けそうですが）`String -> IO ()` な函数 `solve` を haskell で書いて export, それを js から呼ぶ方向でやってみましょう[^1]．

## Haste での export のしかた

これは公式の [readme](https://github.com/valderman/haste-compiler#interfacing-with-javascript) にだいたい書いてあります．
今回はこういうふうにするのが良さそうです：

i) まず文字列を受け取って答える函数 `solve` を書いて export. `solve` は haskell で提出した時のコードをほとんどそのまま流用すればよいです．

```hs:solver.hs
{-# LANGUAGE OverloadedStrings #-}
import Haste
import Haste.Foreign (export)

-- 入力文字列を受け取って所定の動作
solve :: String -> IO ()
solve ln = let (a:b:c:d:_) = map (read :: String -> Int) . words $ ln
               in
                    Haste.writeLog $ case (b*c) `compare` (a*d) of
                        GT -> "TAKAHASHI"
                        EQ -> "DRAW"
                        LT -> "AOKI"

main = export "solve" solve
-- main で函数を export します．これで .js から Haste.solve として呼べる
```

ii) ついで caller.js からこれを呼ぶ

```js:caller.js
function Main(){
  Haste.solve(require("fs").readFileSync("/dev/stdin", "utf8"));
}
```

コンパイルは次のようにします．

```bash:terminal
$ hastec '--start=$HASTE_MAIN(); Main();' --with-js=caller.js solver.hs
```

これはこういう意味です:

> 「ねえねえ Haste さん，まず普通に .hs の main 函数を実行して(つまりこの場合 export をして)，それから `Main();` を呼んでください．`caller.js` を一緒に使って，そういうつもりで `solver.hs` をコンパイルしてくださいな」

最終産物は `solver.js` になります[^2]．試してみましょう

```bash:terminal
$ echo "5 2 6 7" | node solver.js 
# AOKI
```

いいですね．

## 文字数制限

AtCoder に提出できるのは 60000 字まで．Haste の吐くプログラムはそう大きくは無いのですが，minify 無しでは伸び伸びコードを書かはる[^3]ので，短くしてもらいましょう．Haste には closure-compiler が同梱されています．

```bash:terminal
$ hastec '--start=$HASTE_MAIN(); Main();' --with-js=caller.js solver.hs -O2 --opt-all # 下の追記も参照
```

しかしこれでは実行時エラーになってしまいます．どうやら `readFileSync` とかも最適化して `l` とかに名前を変えてしまわはるらしい．closure-compiler に `--opt-minify-flag=FLAG` の形式でオプションを渡せるので，多分それで上手いことできるのですが，普段 closure-compiler 使わないのでどうしていいかわからず，いっそこうしてしまいました．

```js:solver.js
function Main(){
  Haste["solve"](require("fs")["readFileSync"]("/dev/stdin", "utf8"));
}
```

これで（とりあえず） minify 後も問題なく動作します．

**追記** (31 Oct 2015) : `hastec` の HEAD をお使いの場合，ごく最近オプションの[自由度が上がり](https://github.com/valderman/haste-compiler/commit/53553b04180d4cce4f9fe13f8a31e54b9b9fc35c#diff-de5c5242c39755f25d79888b96def1e0L61), `--opt-all` は (minify をする) `--opt-minify` を含まなくなりました．最新版をお使いの方は `--opt-minify` などを別に指定してコンパイルしてください．

# まとめと結果
ファイルを纏めて gist に[あげました](https://gist.github.com/lesguillemets/69d089a3c4eb47a2de3b)．

結果

[Submission #539613](http://abc030.contest.atcoder.jp/submissions/539613)

![yarimashita.png](https://qiita-image-store.s3.amazonaws.com/0/63937/ec593413-e27f-95c8-cf66-cca9935ba8fd.png)

やりましたね．

# 蛇足

というわけで，ネタ記事の皮をかぶった Haste の export やってみた記事でした．260 ms というのをどう解釈していいかわからないんですが，他の方の javascript での submission が意外なほど少ないながらそれらを眺めて，自分でも普通に javascript で[書いた](http://abc030.contest.atcoder.jp/submissions/549464) (344ms) のも同じかそれ以上の時間がかかっているところを見ても，おそらくほとんどの時間は node の起動とか I/O とかそのへんに割かれているのでしょう．少なくともこの問題については(つまり，殆ど何もしない時点では) Haste によっての時間差は無視できそうに見えます．

もちろん atcoder は Haskell での提出を受け付けているので Haste を使う利点は皆無です．Haste がどこまで戦えるのかにはちょっと興味がありますが，多分出入力のチューニングがまず必要になるだろうという気がします．入力が非常に小さくて計算の多い問題を拾ってこられればベンチマーク的な興味も出てくるかもしれませんが，ふつうに書きたいものを書くのが幸せでしょう．

----

[^1]: あるいは，`String -> String` を haskell で書いて javascript で `console.log(Haste.solve(input));` とするのも良さそうです．
[^2]: ここでいう `caller.js` をコンパイルする `.hs` と同じ名前にしてしまうと名前が衝突してしまいます．その結果 "file is locked" といって[怒られる](https://github.com/valderman/haste-compiler/pull/274) のでcaller の名前を変えるか `-o` オプションでコンパイル先の名前を変えるかしましょう．
[^3]: 逆に，error とかが出た時に元の haskell のどこにあたるエラーかとかを教えてくれるようにコンパイルすることも出来ます．