5
3

More than 5 years have passed since last update.

Haste で atcoder に挑戦してみr…ん?

Last updated at Posted at 2015-10-25

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 の公式アカウントより

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

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 にだいたい書いてあります.
今回はこういうふうにするのが良さそうです:

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

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 からこれを呼ぶ

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

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

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

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

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

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

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

いいですね.

文字数制限

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

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 使わないのでどうしていいかわからず,いっそこうしてしまいました.

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

これで(とりあえず) minify 後も問題なく動作します.

追記 (31 Oct 2015) : hastec の HEAD をお使いの場合,ごく最近オプションの自由度が上がり, --opt-all は (minify をする) --opt-minify を含まなくなりました.最新版をお使いの方は --opt-minify などを別に指定してコンパイルしてください.

まとめと結果

ファイルを纏めて gist にあげました

結果

Submission #539613

yarimashita.png

やりましたね.

蛇足

というわけで,ネタ記事の皮をかぶった Haste の export やってみた記事でした.260 ms というのをどう解釈していいかわからないんですが,他の方の javascript での submission が意外なほど少ないながらそれらを眺めて,自分でも普通に javascript で書いた (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" といって怒られる のでcaller の名前を変えるか -o オプションでコンパイル先の名前を変えるかしましょう. 

  3. 逆に,error とかが出た時に元の haskell のどこにあたるエラーかとかを教えてくれるようにコンパイルすることも出来ます. 

5
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3