yunomuのブログ

酒とゲームと上から目線

Haskellでdcっぽいのを作る その1

Haskellやると彼女ができる」という都市伝説がある。

そういえばスタートHaskell第0回参加の時のアンケートで「なぜHaskellを始めたいか」って聞かれて「彼女ができると聞いて」って答えた気がする。そして私はHaskellを勉強しはじめてしまったので、今私に彼女ができたら「ホラやっぱりHaskell始めたからね」とか言われてしまうらしい。酷い話である。
なんか悔しいので関係ないけど「Haskell書くと痩せる」という都市伝説を作って放流しておきました。Pythonは痩せない。

そんなわけで、彼女を作るために日々頑張ってます。(Haskellを)
Haskellで彼女を作ればいいじゃん。(人工知能の本を開きながら)

まあどうでもいいや。
最近はErlangで遊んでたんですけど、やっぱりちょっとHaskell書いてみたくなって戻って来ました。というのが本当のところです。

あ、今は酒飲んでません。

で、できたのがこれです。
dc_haskell at master from yunomu/exercises - GitHub
UNIXのdcコマンドっぽいのを作りたかった。いわゆる逆ポーランド記法の電卓です。
逆ポーランド記法だと、演算子順位構文解析なんてしなくていいし、スタックがあれば作れるので楽かなぁと思って。使いやすいですしね。なんで逆ポーランド記法の電卓ってあんまし売ってないんですかね。少なくとも日本ではもっと普及しててもいいはず。だって「1と2を足してそれに3をかける」が「1,2,+,3,*」と書ける……だとあんまし普通の電卓と変わりませんけど、「1と2に3をかけたものを足す」が「1,2,3,*,+」って書けるんですよ。日本語のまんまじゃないですか。

話がそれましたが、
電卓を作りました。電卓は入出力とか文字列処理とか計算とかがひと通りあるので、Webサーバに並んでお勉強用に実装する事が多いシステムです。いろんな言語で何度も実装してきましたが、こんなに手こずったのは最初のCの時以来かもしれません。

とりあえずparseを使ってみたかったので「プログラミングHaskell」からparserの一部をコピペしてParserモジュールを作りました。とはいえ単に整数を読むために使っているだけなので、dc.hsの中のparse int xsの部分は実はread xsとかでもいいです。

calc :: Stack Int -> String -> Stack Int
calc s xs
    | xs == "+" = ope s (+)
    | xs == "-" = ope s (-)
    | xs == "*" = ope s (*)
    | xs == "/" = ope s div
    | otherwise = case parse int xs of
                    [(v, "")] -> push s v
                    _         -> error "invalid input."

ここを

calc :: Stack Int -> String -> Stack Int
calc s xs
    | xs == "+" = ope s (+)
    | xs == "-" = ope s (-)
    | xs == "*" = ope s (*)
    | xs == "/" = ope s div
    | otherwise = push s (read xs)

こんな感じ?

あとエラーのハンドリングがうまくいってなくて、変な文字列を入れた時のエラー処理がpとかqを実行するまで表にでてこないという。直し方はわかるんだけど、なんでそう動くのかよくわかんなくて、ちょっと放置している。

それととりあえず適当にって感じのopeが

ope :: Stack Int -> (Int -> Int -> Int) -> Stack Int
ope s f = push s2 (f v1 v2)
          where
            (v1, s1) = pop s
            (v2, s2) = pop s1

これなんかかっこ悪い気がする。こっちもエラー飛ばないし。そもそもStackを

data Stack a = Stack [a]

って定義してるのも、これもなんか本当にこれでいいのかなぁという感じ。よくわかんないんですけど。

あとインタプリタだと動くけど、コンパイルするとプロンプトが出ない。出ないほうがdcっぽいから消しちゃうという手もあるんだけど、また例によってpとかqを実行するまでプロンプトの出力が遅延されてる。
やはりモナドか。遅延評価はある意味厄介というか、意識しないとうまく動きませんね。

なんかスッキリしないのでなかなかErlangに行けない。
今回は酒飲んでません。