序
Haste-compiler は Haskell から javascript へのコンパイラで,javascript との連携も(js -> haste (Haste.Foreign.ffi
)も, haste -> js (Haste.Foreign.export
)も)考えられています.
atcoder ではプログラミングコンテストが定期的に行われており,javascript (node.js) など様々な言語で解答を提出することができます.
……ティン!!!
ということで
たとえば Atcoder Beginner Contest 030 - A を解いてみます.
stdin を読む
まず node.js でどのように出入力をするのか見てみましょう.
twitter での AtCode の公式アカウントより
AtCoderで使える言語としてJavaScript(node.js)を追加しました!(入出力は http://t.co/Ukh28vk7 を参考にしていただけると良いと思います!
— AtCoder (@atcoder) June 11, 2012
リンク先を参照するとこういう感じでやるのがいいみたいです:
// input :: String, 入力全体を受け取って解く
function Main(input) { ... }
// 入力を読み込んで Main に渡す
Main(require("fs").readFileSync("/dev/stdin", "utf8"));
というわけで,(全部 Haste でも書けそうですが)String -> IO ()
な函数 solve
を haskell で書いて export, それを js から呼ぶ方向でやってみましょう1.
Haste での export のしかた
これは公式の readme にだいたい書いてあります.
今回はこういうふうにするのが良さそうです:
i) まず文字列を受け取って答える函数 solve
を書いて export. solve
は haskell で提出した時のコードをほとんどそのまま流用すればよいです.
{-# 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 からこれを呼ぶ
function Main(){
Haste.solve(require("fs").readFileSync("/dev/stdin", "utf8"));
}
コンパイルは次のようにします.
$ hastec '--start=$HASTE_MAIN(); Main();' --with-js=caller.js solver.hs
これはこういう意味です:
「ねえねえ Haste さん,まず普通に .hs の main 函数を実行して(つまりこの場合 export をして),それから
Main();
を呼んでください.caller.js
を一緒に使って,そういうつもりでsolver.hs
をコンパイルしてくださいな」
最終産物は solver.js
になります2.試してみましょう
$ echo "5 2 6 7" | node solver.js
# AOKI
いいですね.
文字数制限
AtCoder に提出できるのは 60000 字まで.Haste の吐くプログラムはそう大きくは無いのですが,minify 無しでは伸び伸びコードを書かはる3ので,短くしてもらいましょう.Haste には closure-compiler が同梱されています.
$ hastec '--start=$HASTE_MAIN(); Main();' --with-js=caller.js solver.hs -O2 --opt-all # 下の追記も参照
しかしこれでは実行時エラーになってしまいます.どうやら readFileSync
とかも最適化して l
とかに名前を変えてしまわはるらしい.closure-compiler に --opt-minify-flag=FLAG
の形式でオプションを渡せるので,多分それで上手いことできるのですが,普段 closure-compiler 使わないのでどうしていいかわからず,いっそこうしてしまいました.
function Main(){
Haste["solve"](require("fs")["readFileSync"]("/dev/stdin", "utf8"));
}
これで(とりあえず) minify 後も問題なく動作します.
追記 (31 Oct 2015) : hastec
の HEAD をお使いの場合,ごく最近オプションの自由度が上がり, --opt-all
は (minify をする) --opt-minify
を含まなくなりました.最新版をお使いの方は --opt-minify
などを別に指定してコンパイルしてください.
まとめと結果
ファイルを纏めて gist にあげました.
結果
やりましたね.
蛇足
というわけで,ネタ記事の皮をかぶった Haste の export やってみた記事でした.260 ms というのをどう解釈していいかわからないんですが,他の方の javascript での submission が意外なほど少ないながらそれらを眺めて,自分でも普通に javascript で書いた (344ms) のも同じかそれ以上の時間がかかっているところを見ても,おそらくほとんどの時間は node の起動とか I/O とかそのへんに割かれているのでしょう.少なくともこの問題については(つまり,殆ど何もしない時点では) Haste によっての時間差は無視できそうに見えます.
もちろん atcoder は Haskell での提出を受け付けているので Haste を使う利点は皆無です.Haste がどこまで戦えるのかにはちょっと興味がありますが,多分出入力のチューニングがまず必要になるだろうという気がします.入力が非常に小さくて計算の多い問題を拾ってこられればベンチマーク的な興味も出てくるかもしれませんが,ふつうに書きたいものを書くのが幸せでしょう.
-
あるいは,
String -> String
を haskell で書いて javascript でconsole.log(Haste.solve(input));
とするのも良さそうです. ↩ -
ここでいう
caller.js
をコンパイルする.hs
と同じ名前にしてしまうと名前が衝突してしまいます.その結果 "file is locked" といって怒られる のでcaller の名前を変えるか-o
オプションでコンパイル先の名前を変えるかしましょう. ↩ -
逆に,error とかが出た時に元の haskell のどこにあたるエラーかとかを教えてくれるようにコンパイルすることも出来ます. ↩