4.Currency Converter
2回目の話題は外国為替変換である。ドルとユーロでの変換を自動的に行ってくれるもので、エントリーは、ドルとユーロの金額入力(表示も)の二つである。マウスでどちらかのエントリーをクリックすると、クリックしたエントリーがフォーカスされ、入力できるようになる。キーから数字が入力されると、瞬時に、相手側の通貨にも反映される。即ち、50ドルを入力しようとして、最初に5を入力すると、5ドルに対するユーロの金額が表示される。続いて0を入力すると、50ドルに対応したユーロの金額が表示される。また、数字以外の文字記号が入力された時は、相手側には、"-"が表示される。
4.1 フレーム、パネル、テキストフィールド
前回は、フレームとテキストフィールドであったが、今回は、フレームの上にパネルを張り、その上に、二つのテキストフィールドを作成する。これは、次のようにする。なお、フレームのラベルは"Currency Converter"とする。
f <- frame [ text := "Currency Converter", tabTraversal := True ] p <- panel f [] -- Use panel for tab traversal dollar <- entry p [] euro <- entry p []
パネルの表示画面上での大きさを次のように与える。なお、パネルにもラベルを付けて、"Amounts update while typing."とする。
set p [layout := margin 10 $ column 10 [ grid 10 10 [[label "Dollar:", widget dollar], [label "Euro:" , widget euro ]] , label "Amounts update while typing." ]]
フレームも同じように配置する。ついでに、ドルのテキストフィールドをフォーカスする。
set f [layout := widget p]
focusOn dollar
初期画面は次のようになる。
4.2 金額の入力と相手側通貨での金額
それでは通貨を入力し、相手側の通貨での金額を求める関数withStringを説明しよう。これは次のようになっている。
withString f s = maybe "-" (printf "%.2f") . fmap f $ listToMaybe [x | (x,"") <- reads s]
前回はreadsの説明をしなかったが、これは便利な関数である。
これと似た関数で、readを知っていると思うが、これは文字列を受け取り、readのインスタンスの型の値を返していた。
例えば、
read "True"
は、インスタンスの型がBoolなので、その値のTrueを返す。
read "3" + 7
は、インスタンスの型がNumなので、その値の10を返す。
しかし、
read "3"
とすると、エラーとなる。これは、インスタンスの型が定まらないためである。次のような場合は大丈夫である。
read "\"3\"" ++ "567"
readだと、インスタンスの型が要求しているものと異なるときはエラーを起こす。これを避けたい場合に利用するのが、readsである。例で見ていくことにする。今、50ドルをユーロに替えたときの金額を知りたかったとする。この時、50と入力するので、次のようになる。
Prelude> reads "50" :: [(Float, String)] [(50.0,"")]
また、誤って、50zと入力したとすると、次のようになる。
Prelude> reads "50z" :: [(Float, String)] [(50.0,"z")]
あるいは、$50と入力したとすると、
Prelude> reads "$50" :: [(Float, String)] []
このように入力に誤りがあったとしても、プログラムは停止することなく、どのようなことが起きているかを伝えてくれる。
入力された値は、readsでいったんリストに替えられ、内包表記で正しく入力されたものだけが、リストとして返される。次の例を見て欲しい。
Prelude> [x| (x,"") <- (reads "50" :: [(Float, String)])] [50.0] Prelude> [x| (x,"") <- (reads "50z" :: [(Float, String)])] [] Prelude> [x| (x,"") <- (reads "$50" :: [(Float, String)])] [] Prelude>
ここで得たリストを、Maybe Float型に変更すると次のようになる。
Prelude> import Data.Maybe Prelude Data.Maybe> listToMaybe [x| (x,"") <- (reads "50" :: [(Float, String)])] Just 50.0 Prelude Data.Maybe> listToMaybe [x| (x,"") <- (reads "50z" :: [(Float, String)])] Nothing Prelude Data.Maybe> listToMaybe [x| (x,"") <- (reads "$50" :: [(Float, String)])] Nothing
入力間違いはNothingで、数値はJustで返ってくる。
関数withStringでの変数fは、レートを計算するための式である。このfを用いて、相手側通貨での金額が得られる。入力された金額は、Maybe Float型なので、fmap fで相手通貨への変更を行う。この時、出力は、入力に間違いがあるときは"-"が、そうでなければ金額が出力される。
この関数を利用して、ドルからユーロ、ユーロからドルへの変更は次のようになる。
dollarOut, euroOut :: Behavior String dollarOut = withString (/ rate) <$> euroIn euroOut = withString (* rate) <$> dollarIn
ここで、euroInとdollarInは、dollarとeuroのテキストフィールドの振舞いを表したもので、次のように定義されている。
euroIn <- behaviorText euro "0" dollarIn <- behaviorText dollar "0"
4.3 金額の変更をアニメーションに
それぞれの振舞いをアニメーションの用に示せばよいので、次のようになる。これは、金額に変更があった時にイベントが発生し、表示が変わることに注意してほしい。
sink euro [text :== euroOut ] sink dollar [text :== dollarOut]
4.4 プログラム全体
プログラムは、このホームページに掲載されいるが、転載する。
{----------------------------------------------------------------------------- reactive-banana-wx Example: Currency Converter ------------------------------------------------------------------------------} import Data.Maybe import Text.Printf import Graphics.UI.WX hiding (Event) import Reactive.Banana import Reactive.Banana.WX {----------------------------------------------------------------------------- Main ------------------------------------------------------------------------------} main :: IO () main = start $ do f <- frame [ text := "Currency Converter", tabTraversal := True ] p <- panel f [] -- Use panel for tab traversal dollar <- entry p [] euro <- entry p [] set p [layout := margin 10 $ column 10 [ grid 10 10 [[label "Dollar:", widget dollar], [label "Euro:" , widget euro ]] , label "Amounts update while typing." ]] set f [layout := widget p] focusOn dollar let networkDescription :: MomentIO () networkDescription = do euroIn <- behaviorText euro "0" dollarIn <- behaviorText dollar "0" let rate = 0.7 :: Double withString f s = maybe "-" (printf "%.2f") . fmap f $ listToMaybe [x | (x,"") <- reads s] -- define output values in terms of input values dollarOut, euroOut :: Behavior String dollarOut = withString (/ rate) <$> euroIn euroOut = withString (* rate) <$> dollarIn sink euro [text :== euroOut ] sink dollar [text :== dollarOut] network <- compile networkDescription actuate network