完全なる関数の電卓 〜はじめてのGUIアプリ〜
もうすぐ期末テストなのに何もしていないのだが…
まぁ、それは置いておいて。
wxHaskell (その2)
GUIもHaskellでやりたいよ〜ということで(そうだったのか?)
昨日からwxHaskellをいじっているのだが、
色々と問題がありそうだったのは昨日書いたとおりである。
- ファイルサイズ
適当にZipで圧縮すると1.58MBに、
7Zipで圧縮すると970KBぐらいになった。
これでも相当でかいと思うのだが、結局実行するときは解凍しないといけないので
根本的には解決にならないだろう。
実行ファイルを圧縮できるUPXもつかってみた。
普通に圧縮したら6MB弱にしかならなかったのでこりゃ駄目だと
思っていたら余分なデータの削除なるオプションを使ったら500KB弱になった。
まぁ、それなりに大丈夫なレベルか?
ちょっと大きいような気もするけど7MBから考えると大幅にましである。
- 起動時ウインドウがちらつく
表示するオプションとかが分かったので解決
- コンソールが表示される
分からず。これってPEファイルのオプションじゃなかったっけ?
というわけでwxHaskellの勉強である。
とりあえずなんか作らねば。というわけで、電卓である。
関数型なので、関数電卓。作ったのは関数電卓じゃないけど。
module Main where import Graphics.UI.WX -- 電卓の状態 type CalcState = (Integer,Maybe Integer,Maybe String) initState = (0,Nothing,Nothing) main = start mainFrame -- ウインドウの形成 mainFrame = do f <- frameFixed [text := "Calculator" ,clientSize := sz winX winY ,visible := False] p <- panel f [] file <- menuPane [text := "&File"] mclose <- menuItem file [text := "&Close"] disp <- textEntry p AlignRight [text := "0",outerSize := sz (winX-5) textHeight] var <- varCreate initState set f [menuBar := [file] ,on (menu mclose) := close f] makeButton p (disp,var) set f [visible := True] where makeButton frame arg = sequence [mak x y t | (y,ls) <- zip [0..] but ,(x, t) <- zip [0..] ls] where mak x y t = button frame [text := t ,position := pt (x*(bSizeX+bMergin)+bMergin) (y*(bSizeY+bMergin)+textHeight+bMergin) ,outerSize := sz bSizeX bSizeY ,on command := pushButton arg t] but = [ ["7","8","9","/","AC"] ,["4","5","6","*"] ,["1","2","3","-"] ,["0","+/-",".","+","="] ] (bSizeX,bSizeY) = (30,20) bMergin = 5 textHeight = 20 (winX,winY) = (182,145) -- ボタン押下時の処理 pushButton :: (TextCtrl a,Var CalcState) -> String -> IO() pushButton (disp,var) b | b=="AC" = do varSet var initState dispNum disp initState | any (==b) ["0","1","2","3","4","5","6","7","8","9"] = upd $ pushNum $ read b | any (==b) ["+","-","*","/"] = upd (pushOpr b) | b=="=" = upd pushEqual | b=="+/-" = upd pushMinus where upd f = do dat <- varGet var let new = f dat varSet var new dispNum disp new -- 数字の表示 dispNum :: TextCtrl a -> CalcState -> IO () dispNum disp (n,Nothing,_) = set disp [text := show n] dispNum disp (_,Just n ,_) = set disp [text := show n] -- 各々の処理 pushNum :: Integer -> CalcState -> CalcState pushNum d (n,Nothing,o) = (n,Just d,o) pushNum d (m,Just n,o) = (m,Just (n*10+d),o) pushOpr :: String -> CalcState -> CalcState pushOpr o (n,Nothing, _) = (n,Nothing,Just o) pushOpr o (m,Just n,Nothing) = (n,Nothing,Just o) pushOpr o (m,Just n,Just s) = (exec s m n,Nothing,Just o) pushEqual :: CalcState -> CalcState pushEqual (m,Nothing, _) = (m,Nothing,Nothing) pushEqual (m,Just n,Nothing) = (n,Nothing,Nothing) pushEqual (m,Just n,Just o) = (exec o m n,Nothing,Nothing) pushMinus :: CalcState -> CalcState pushMinus (m,Nothing,o) = (-m,Nothing,o) pushMinus (m,Just n,o) = (m,Just (-n),o) exec opr m n = op m n where op = case opr of "+" -> (+) "-" -> (-) "*" -> (*) "/" -> div
ええと…ここに張るには長すぎだったかも。
一応ソースとWindows用バイナリ。(↑と同じもんだけど)
http://fxp.hp.infoseek.co.jp/haskell/calc.zip
割と普通である。
Windows電卓を参考にしたけど、結局面影なし。
機能も整数の演算のみ。小数点ボタンがあるけど、
押すとおちるので押さないように。
プログラムについて。
mainFrameがウインドウの構築を行う。
ここはまぁ、なんというか普通。
IOモナドでどろどろと作ってます。
イベントハンドラだが、
どうやって状態の更新を行っているかというと、
var <- varCreate initState
varCreateというのは a -> IORef a の型を持つ関数で、IORefは
varGet :: IORef a -> IO a varSet :: IORef a -> a -> IO ()
などの操作が行える、要するにポインタのようなものだと思えば。
これをみんなで共有して状態を読み書きしている。
作ってて思ったけど、これHaskell?
てか、こんなソース書いててHaskell使う意味有るのん?
ほとんどコマンドの羅列やないの?
(いや、wxHaskellのサンプルで使われてるんだって)
それでも何とか最終的な計算部分だけは関数的になるように頑張って見た。
しかし、もっと何というか関数的に宣言的に記述したいのである。
コマンドの羅列で作ってると、これは手続き言語か、と思えてくる。
とくにIORefなど使う羽目になるとは…
まぁ、もうちょっと頑張ってみますか…