wxHaskell (その2)

GUIHaskellでやりたいよ〜ということで(そうだったのか?)
昨日から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など使う羽目になるとは…
まぁ、もうちょっと頑張ってみますか…