眞々田泰裕

1　はじめに
GUIは従来命令型言語による実装が主だっていた.しかし大規模なGUIになると変数や関数の依存関係が複雑化し,それ故に予期せぬシステム異常が発生し易い.そういった問題の解決法として注目されたのがオプジェクト指向プログラミングであり,現在ではオプジェクト指向の性質を持ったプログラミング言語によるGUIが主流となっている.しかしそれもいいところばかりではない.規模が大きくなればオプジェクト同士の関係やメッセージの流れがつかみにくくなり,それによってエラーが発生したり,原因箇所の発見や修正が難しくなり易い.それに対し,近年は関数型言語を用いたGUIの実装が注目されている.関数型言語が特徴として持つ参照透明性はデータのや関数の依存関係を捉えやすいものとし,エラーの発生を軽減し,発見し易くすることで強固なシステムを確立することができる.その中でもHaskellをはじめとする型に厳密な言語であればその性質はより増強される. GUIは副作用を伴うものであるため,その点に関してはなるべく純粋であらんとする関数型言語に適合しないようにも思われる.しかしそれでも関数型言語の特長を副作用のある領域で有効に使うためにFRP(Functional Reactive Programming) [5]というフレームワークがある.そこで本論文では関数型言語の中でも特に型に厳密なHaskellでFRPを活用し, GUIの実装における
可能性を模索する.
その目的を基に以前Neooey [10]というGUIライプラリを作成した. これは別のGUIライプラリPhooey [3]を元に作られている.  Phooeyは小さなGUIユニットを関数的に結合させることによってより大きなGUIユニットを作ることができる特長を持つ.このPhooeyの性質は魅力的であるがその一方でPhooeyによって実装可能なGUIの範囲は狭く,大規模な例に応用することができなかった.その要因の一つと考えたのがGUIユニットの構造である. PhooeyにおけるGUIユニットはインターフェースを担うレイアウトの情報と内部関数を担うイベント処理を一つにまとめた構造をしている. この時GUIユニットを結合させる際にレイアウトと内部関数も同時に結合するのでコードの可読性が高まり,GUIユニットを直感的に扱いしやすくするメリットはあるが,それ故に実装の柔軟性に欠ける.こういった問題を解決させるため, Neooeyとしての出発点はレイアウトと内部処理を分離するというところにあった.実際にNeooeyでははPhooeyが持つGUIユニットの結合性をレイアウトと内部処理のそれぞれで保持し,その上でGUIの表現性を高めることに成功したがその一方で課題もいくつか見つかった.今回作成したYeooeyはNeooeyを改良したものである.特徴をまとめると次の通りである.
●ウィジェットアクションの実装にYampaを導入:  Neooeyでは個々のイベント処理を行うウィジェットアクションを関数的に結合させて大きなウィジェットアクションを形成するものとなっていた.このとき形成されるウィジェットアクションはpushベースのFRPに基づくものであった. Yeooeyではウィジェットアクションの実行にシグナル関数を活用し, pushベースとpullベースの双方のFRPに基づく処理を可能とした.シグナル関数の処理部分と副作用を伴う入出力部分を分離して用意することによって入出力の関係や値の流れを読みやすくする設計がなされている.また,本論文ではYeooeyを用いてGUIの例を実装し,使用方法の紹介やNeooeyとの比較を行う.論文の構成については次の通りである.第2章で関数型言語におけるGUIプログラミングの手法であるFRPについて説明する.第３章でYeooeyの背景にあるGUIツールキット及びライプラリについて説明する.第4章でYeooeyの実装とを行い,Neooeyとの比較を行う.また,Yeooeyを用いたGUIの作成法を例を挙げて説明する. そして第5章で関連研究,第6章で結論を述べる.
2 FRP
命令型プログラミングやオプジェクト指向プログラミングをはじめとするプログラミングバラダイムは多数存在する.そのひとつにFRP(Functional Re- active Programming)がある. FRPはRP(Reactive Programming)に関数型の特徴を組み合わせたバラダイムである.  FRPについて述べるために,まずはRPについて言及する. RPはGUIや株システム,ロボットなどの外界とのやり取りが多く存在するような副作用を伴うコーディングで有用なバラダイムの一つである.副作用が多いコードは入出力の正当性を迫うのが難しく,予期せぬ値の代入や変更が行われたりすることでバグが発生し易く,原因の特定がしにくくなる.例えば伝統的なバラダイムのひとつである命令型プログラミングは内部で用意する変数や行わせたい命令,イベントの設定を実行させたい順番に記述する.このようなコーディングはその命令周辺の処理の流れを把握する上では直感的でわかりやすいかもしれないがイベントや変数の数が増えて規模が大きくなると互いの依存関係を捉えることが困難になり,不正な　いがあるが深い関連性もある.リアクティプは初期値値の参照が発生し易くなる.それに対してRPでは入及び値の変化を表現するイベント,イベントは最初の出力で取り扱う値を時間によって変化するものとして発生を待つリアクティブとして表現することができ考え,この値との関係を記述してプログラムをくみ上る. Elliottによる論文[4]ではその点を踏まえ,イベげる.ントとリアクティブな値を相互再帰的な定義で実装している.例えば次のような例を考える.このような時間によって変化する値をふるまい関数のもとで動かすことによってFRPに基づいたコードが表現される.  FRPで設計されている関数部分は純粋なので，値の正当性を保証する. x = 3; y = 2 * x; x = 5; print (y);伝統的なバラダイムでは6が印字される. yの値を決定する際の±の値は3であり,その後で±の値が変更されてもyの値に影響を及ぼすことはない.対してRPにおいては10が印字される.  2行目のy = 2 * zは時間によって変化する個ェとyの関係として宣言されたものであり,ェの値がその後変化するとそれに反応するようにyの値が再計算される.この考え方は予期せぬところで変数の中身が変わってもそれぞれの変数の関係の記述によって各値の再計算が行われるためにバグが発生しにくく,値を追いやすくなり保守性が高まる.また一般にRPのコードは長さが短くなるという特長がある.尚,RPにおいて時間によって連続的に変化する値のFRPはリアクティプやふるまい関数を処理するタイミングによってpush方式とpull方式の二つの駆動方式に分類することができる. push方式は一端の値が変化した際,その変化を他の値にも伝播させ,芋づる式に再計算を行う方式である.つまりイベントが発生したときにそれに合わせて再計算を行うことが一般的である. 一方でpull方式は一端の値が変化した時ではなくその値の参照が行われる時に再計算を行う方式である.定められたサンプリング問隔に合わせて各値の再計算を行うことが一般的である.この二つの方式の優位性は値が変化する頻度によって左右される.変化の頻度が高い場合,その変化を逐一伝えるpush形式ではタイムリークやスペースリークを多く生じさせる原因となる.そのためその情報が必要となるタイミングで再計算を行うpull形式の方が優れている．逆に変化の頻度が低い場合はpush形式の方が優れている. 彼の変化に即時反応して計算させることができるためタイムラグが小さくて済むためである. FRPにもいくつか種類があり,そのうち本論文と関ことをリアクティブ(reactive),リアクティブ同士のデータフローをふるまい(behavior)と呼ぶ.また，時間によって離散的に与えられる値をイベント(event)と呼ぶ。 RPの考え方は名称以上に浸透しており,例えばMicrosoft Excelなどの装計算ソフトでその考え方が使われている.係するのはAFRP(Arrows-based Functional Reac- tive Programming)である( [7], [9], [1]).  AFRPはアローの一種であるシグナル関数(signal function)を利用してFRPを体現したものである.  AFRPにおいてイベントやリアクティプはシグナル(signal)と呼ばれる.そしてそれらのふるまい関数に当たるのがシグナル関数である.シグナル関数はふるまいのようなものでシグナルこれらの考え方を関数的に実現したものがFRPである. FRPはElliott, Hudakによる論文Fran [5]が原形になっている.  FRPでは時間によって変化するリアクティブやイベントを主にストリーム(stream)で表現する.ストリームは時間によって変化する値のリストとして表すことができ,次のように時間と値の連想リストとして考えることができる. (signal)と呼ばれるリアクティプを変換する関数である。 AFRPでは複数のシグナル関数を結合し，大きtype Stream a = [(Time, Value a)]リアクティプとイベントは連続的と離散的という造なシグナル関数をつくることでプログラムの構築を行う.
3   Yeooeyの背景にあるGUIライブラリ
この章ではYeooeyの開発に当たってその内部で使用されているYampa, wxHaskell及びYeooeyの以前のバージョンであるNeooeyについて言及する.
3.1 Yampa
HaskellにおけるAFRPの伝統的なライプラリとしてYampaが挙げられる.  YampaはFran [5]に基づいて開発されたHaskellのGUIライプラリであり, Yale Haskell Group[12]によって開発された. FRPとしての基本設計はpull方式に基づいている. Yampaではシグナル関数を表現するための型としてSFが使われる. data SF a b SF型はアローとして扱うためのプリミティプな演算子がいくつか用意されており,それらを活用して一つのシグナル関数を構築する.以下にその演算子の例を載せる. arr :: (a -> b) -> SF a b (>>>) :: SF a b -> SF b c -> SF a c first :: SF a b -> SF (a, id) (b, id) second :: SF a b -> SF (id, a) (id, b) (&&&) :: SF a b -> SF a b’ -> SF a (b,b’) loop :: SF (a, p) (b, p) -> SF a b loopPre :: b -> SF (a, p) (b, p) -> SF a b
3.2 wxHaskell
wxHaskell[8]はC++で記述されているGUIツールキットwxWidgetsをHaskellで扱えるようにバインディングしたGUIツールキットである.現在も更新は行われており,公式ページ†¹から無儀で入手することができる.ポタンやテキストコントロールなどのGUIを構成するオプジェクトをウィジェットと呼ぶ。 wxHaskellでは専用の関数でウィジェットを生成することができる．このとき同時にウィジェットハンドラが用意され,このハンドラを通してウィジェットに視覚的な変更,あるいは内部の処理を要求することができる. GUIウィジェットはそれぞれプロバティを保持している.プロバティはいくつかの属性の集合で表され,ウィジェット上に表示される文字列やサイズなどの情報が与えられる. wxHaskellでは属性のリストとしてプロバティを表現しており,ウィジェットハンドラを通して属性を定めることが可能となっている.以下にwxHaskellによるGUIの簡単な実装の例を載せる. main :: IO () main = start hello hello :: IO () hello = do f <- frame [ text := "Hello!"] quit <- button f [ text := "Quit" , on command := close f] set f [layout := widget quit]
3.3 Neooey
Neooey  [10]はGUIユニットの結合性を意識して構築されたHaskellの関数型ライプラリである.そのベースとなるGUIライプラリにはwxHaskellが用いられている. このライプラリはElliottによって開発されたPhooey [3]の問題点を解決させるべく実装したものである.  PhooeyにおいてはUIと呼ばれるGUIユニットがレイアウトとふるまい関数の両方を同時に保有するように設計されている.そのことは手軽にGUIを構成できるメリットがあるが,その一方でイベントのやり取りが複雑になると実装が難航するという問題があった.そこでNeooeyではレイアウトを保有するGUIユニットとふるまい関数を保有するGUIユニットを分離し,それぞれ設計してからまとめ上げる形にすることによって実装できるGUIの表現幅が広がった.しかしその一方でいくつか問題点も見られた. ● GUIユニットに対して包括的な処理ができない: Neooeyでは各GUIユニットが自身の構造を表現する型をそれぞれ持っている.そしてそれらを結合させた場合はウィジェットを装す型もタプル結合される. uiAppend :: UI (TextCtrl, Button) uiAppend = uiEntry <-> uiButton uiEntry :: UI TextCtrl uiButton :: UI ButtonしかしGUIユニットが保有しているウィジェット情報が異なる場合はリスト概造などの複数のGUIユニットに対して一括で処理を与える操作が制限されてしまう. ● 大規模なGUIユニットの型が長大になる: 前項でも述べた通り, NeooeyではGUIユニットを結合させた場合,ユニットが保有するウィジェット型がタプル結合される.このことはGUIの規模が大きくなればなるほど型が長く複雑になってしまう問題を引き起こす.そこで結合された型にその都度新たな名前を定めることによって可説性を助ける工夫をユーザ側に促していた. ● pullベースで実装することができない: FRPはイベントやデータを処理するタイミングでpush方式とpull方式の2つに区分される. push方式はイベント駆動とも呼ばれ、ユーザの入力などによって外部からイベントが発生した際に状態の再計算を行う方式である. それに対してpull形式はデータ駆動とも呼ばれ,定められた間隔でデータが与えられ,その際に状態の再計算を行う方式である. Neooeyではpush方式で実装するように設計されており, pull方式による実装が行えないようになっていた.これらの問題を解決させることが次章で述べる新しいライプラリの開発の動機づけとなっている.
4 Yeooey
この節では我々が今回実装したGUIライブラリYeooeyとその特徴について述べる.  Yeooeyは以前実装を行ったGUIライプラリNeooeyをベースに改良を加えて汎用性を高めたものである. Neooeyの特長の一つであるGUIをウィンドウやウィジェットのレイアウトを表すUIユニットとGUI内でやり取りする内部データの処理を表すウィジェットアクションを分離して記述する点については共通している.本節ではYeooeyの構造に関して● WidgetType ● UIユニット●ウィジェットアクション● expand関数の順で述べる.また, Yeooeyを用いてGUIを実装する方法を具体例を挙げて述べる.尚,開発に当たって使用したライプラリやモジュールのバージョンはそれぞれwxWidgetsが² 3.0.2, GHCか7.10.3, Yampaか 0.10.7, wxHaskellか 0.92.2.0である.
4.1 WidgetType
Yeooeyではウィジェット構造を表現するためにWidgetType型を導入する. WidgetType型はポタンやテキストコントロールなどの個々のウィジェットを一つのデータ型としてまとめたものである. WidgetType型はその内部で指定されたウィジェットのハンドラを保有する.GUIに対する処理はYeooey内部においてこのウィジェットハンドラを経由して行われる. data WidgetType = NoWidget | WMWin MWin | WWin  Win | WBitmapButton (BitmapButton ()) | WBitmapToggleButton . (中路) | WTextCtrl (TextCtrl ()) | WToggleButton (ToggleButton ()) | WTreeCtrl(TreeCtrl())実際のGUIでは階層的にウィジェットの管理が行われる. そこでYeooeyではウィジェットの階層機造をWidgetTree型で表現する.  WidgetTree型はWidgetType型で表される個々のウィジェットを木構造で管理するものである. type WidgetTree = Tree WidgetType WidgetTree型においてウィジェットハンドラは葉のみが持つようにし、節点にはウィジェットハンドラを持たせない.また,節点が持つ枝の本数は任意の正整数可能であり,設計としてはこれらの枝をリストの形で管理している.
4.2 UIユニット
4.2.1  UIユニットの構造
YeooeyではNeooeyと同様にすべてのGUIをメインフレームとメインコンテナを共通のレイアウトとして持ち,メインコンテナの中で独自のウィジェット構造を構成していくものとしている.そこでGUIの視覚情報を担う構成単位としてUI型を導入する. UI型はメインフレームとメインコンテナを参照して動くアクションとして定義され,さらにWidgetTree型で表されるウィジェット構造とレイアウトの情報を保有している. type UI = MWin -> Container -> IO (WidgetTree, Layout)ここで, MWinはwxHaskellにおけるウィンドウフレーム型Frame (）のシノニムであり, ContainerはPanel ()などのウィンドウ内で使用される梱包ウィジェット型をまとめたデータ型である.そしてLayoutはレイアウト情報を示す型であり,wxHaskellで使用されている同名の型と同一のものである. UI型は直感的には上記のようなメインウィンドウとメインコンテナを引数にして動くアクションとして表現されるが、実際は次のようにモナド変換子を使用した定義として表される. type UI = ReaderT MWin (ReaderT Container (WriterT Layout IO) WidgetTree)先の定義と比較するとReaderTが型引数,WriterTが返り値の型に対応している.モナド変換子は引数を伴うモナド関数を状態を内包する単一のモナドとして扱うことができる.  YeooeyではUIユニットをモナド変換子で定義することで複数の状態を表面上一つのモナドで保有し,結合的に扱えるなどのメリットを得ることができている. UIユニットを構築するためにいくつかの基本的な関数を提供している.そのうちのいくつかを次の小節で紹介する. 4.2.2  UIユニットを新しく生成する一般的なGUIはポタンやテキストエントリなどのいくつかの基本的なウィジェットの組み合わせで構成されている.  Yeooeyではそういったウィジェットを新しく作成するための関数が多数用意されている.これらの関数はプロバティリストを引数にとり,そのウィジェットに行わせる処理を記述するためのウィジェットハンドラをWidgetTreeとして保有するUIユニットを形成する.行わせたい処理は後述するウィジェットアクションによって記述される. プロバティリストはwxHaskellの書式を用いており,作成するウィジェットの種類に応じた属性を指定することができる.以下にその例を示す. buttonUI :: [Prop (Button ())] -> UI textCtrlUI :: [Prop (TextCtrl ())] -> UI buttonUIはボタンウィジェットを作成するための関数である.またtextCtrlUIはユーザによって入力することができるテキストコントロールを作成するための関数である.
4. 2. 3　複数のUIユニットを結合する
前小節の関数などによって作成された複数のUIユニットは最終的に一つに統合される必要がある. それらを行うためにYeooeyではUIユニットを結合するための関数をいくつか備えている.結合関数によってUIユニットを結合するとその内部で保有するWidgetTreeも結合される.このとき新たに生成させるWidgetTreeは根にウィジェットハンドラを持たず,結合されるUIユニットのWidgetTreeをそれぞれ枝として持つ.以下にその例を紹介する. (<->), (<l>) :: UI -> UI -> UI a <-> b = rowUI 5 [a,b] a <l> b = columnUI 5 [a,b] rowUI, columnUI :: Int -> [UI] -> UI二つのUIユニットを連結させる場合は演算子(<->)及び(<I>)を使用する.前者は機方向に,後者は縦方向に結合させる働きを持つ.これらはそれぞれrowUI, columnUIの特殊な例であるため,より細かい指定を行いたい場合はこちらを使用する. rowUIは横方向に, columnUIは縦方向に複数のUIユニットをまとめて連結させることができる.また,第一引数のInt型は配置するウィジェット同士の問隔幅を表している. 4.2.4  UIユニットにレイアウト情報を付与するUIユニットはGUIの視覚的な面を担うものであるため,そのレイアウトを調整するためのレイアウト関数を提供している.これらの関数はUI型の値を変換する形で使用し，この関数らを通してウィンドウ内におけるウィジェットの配置やサイズ,他のウィジェットとの関係などを設定することができる.以下にその例を示す. marginUI :: Int -> UI -> UI  fillUI :: UI -> UI minsizeUI :: Size -> UI -> UI marginUI関数は引数で与えられたUIユニットのレイアウトの端に余白をつくる関数である.この関数はUIユニットのレイアウトにおける最後の仕上げとして使用される場合が多い.また, filluI 関数は与えられたUIユニットのレイアウトを拡大して余白を埋めるレイアウト関数である.更にfillUI関数はUIユニットのサイズを指定する目的で使用する.
4.2.5  GUIを実行する
最終的に,ユーザーによって作成されたGUIは起動する手段が必要となる. Yeooeyでは定義したGUIを起動するための関数としてrunYUI関数を提供している.  runYUI関数はウィジェット構造や視覚的な配置を管理するUIとその内部処理を後述するウィジェットアクションWidActを引数にとりGUIを起動する.
runYUI :: UI -> WidAct’a -> IO ()
4.2.6   Neooeyとの比較
UI型はNeooeyでも導入していたが,ウィジェットハンドラの管理方法に違いがある. Neooeyではウィジェットハンドラそのものやその構造をタプル結合の組み合わせで表現することで管理を行っていた.ウィジェットハンドラの種類や構造が変わると型も変わるため,異なる型の組み合わせでもまとめることができるタプルを使用した経緯があった. NeooeyにおけるUI型は型引数を一つ伴うようになっており,そこにはしばしばタプル結合されたウィジェットハンドラの構造を表すようになっていた.この点については型を見ることで構造がわかるというメリットはあるが,複雑なウィジェット構造を持つUIユニットでは型の表示が長く難解なものとなり,違ったウィジェット構造を持つUIユニットをリストのような一括処理に対して限界があった.それに対してYeooeyにおけるUI型はウィジェット構造によらずその構造はWidgetTree型となる.そのため型引数を伴わずに表現することができるようになり,異なるウィジェット構造を持つUIユニット同士でもリストや木構造で包括的な処理や探寒が可能となった.
4.3ウィジェットアクション
4.3.1ウィジェットアクションの構造
YeooeyではGUIのイベント処理や内部計算などの処理を行うための構造としてウィジェットアクションを導入している.すべてのウィジェットアクションはメインフレームとメインコンテナ,及びWidgetTreeを参照して動くアクションとして定義される.このうちWidgetTreeについてはUIユニットが保有するウィジェット構造と対応しており,ウィジェットアクションをUIユニットに埋め込む際は対象のUIユニットが持つものと一致している必要がある.
にとるようになっている.

type EvWidAct’ sig a = MWin -> Container -> WidgetTree -> SignalHolder sig -> IO a type WidAct = WidAct’ () type EvWidAct sig = EvWidAct’ sig ()また,実際に使用する際はUI型と同様に直接引数を伴う形ではなく,ウィジェットアクションもモナド変換子を使用した定義として表される.これによってUIユニットと同様にウィジェットアクションも単一のモナドとして扱うことを可能にしている.ただしモナドとして扱うメリットを享受するため, EvWidActについてもUIユニットやWidActと同様にモナド変換子による表現が使われる. type EvWidAct’ sig = ReaderT MWin (ReaderT Container (ReaderT WidgetTree (ReaderT (SignalHolder sig) IO))) type WidAct’ = ReaderT MWin (ReaderT Container (ReaderT WidgetTree IO)) type EvWidAct sig = EvWidAct’ sig ()ウィジェットアクションを作成する際はモナドの性質を利用し, do記法や(>>=)激算子などを使用する.ただしウィジェットアクションに対してモナド結合を適用させる際は結合されるそれぞれのウィジェットアクションが保有しているウィジェットハンドラの構造を一致させておく必要がある.もし結合を行いたいウィジェットアクションが一致していない場合は後述するexpand関数を通して構造の拡張を行って一致させる必要がある. 4.3.2 イベントウィジェットアクションイベントウィジェットアクションもウィジェットアクションと同様にモナドとして扱い,結合や生成を行うことができる.また,結合の際には結合されるそれぞれのイベントウィジェットアクションが保有しているウィジェットハンドラの構造を一致している必要がある.この点についても通常のウィジェットアクションと同じである.イベントウィジェットアクションについても保有するウィジェット構造が異なるものを結合させたい場合は後述するexpand関数を通して構造の拡張を行い,一致させる必要がある. 4.3.3シグナル関数をウィジェットアクションにウィジェットアクションを実装する方式のひとつとしてYeooeyではシグナル関数を経由して行う方法を提供している.その方法とはまずユーザー入力など外部からイベントが発生するとシグナルを生成させる.次にそれを入力シグナルとしてGUIの本質的な計算部分を記述したシグナル関数を動かして出力シグナルを生成する.最後にシグナル関数を通して得られたシグナルをもとに盤面を更新させるというものである.このとき,入出力で与えられるシグナルを管理する機能を兼ね備えたウィジェットアクションとしてイベントウィジェットアクションを導入する.イベントウィジェットアクションはほとんどウィジェットアクションと同一のものであるが,シグナルを一時格納するためにSignalHolderを追加で引数埋め込むシグナル関数経由でウィジェットアクションを作る場合はmakeWA関数を使う．この関数はイベントウィジェットアクションとシグナル関数,及びウィジェットアクションを作る関数を引数に取り,それらをまとめ上げたウィジェットアクションを構成する. makeWA :: EvWidAct inp -> SF (Event inp) oup -> (oup -> WidAct) -> WidAct IO a第一引数のイベントウィジェットアクションはシグナル関数に渡す入力シグナルを構成するために用意するアクションである.このアクションはポタンやクリックなどのGUIに対する操作と関連付けられ,行われた操作に応じた入力シグナルを生成する.このシグナルはイベントシグナルとなって第二引数で指定したシグナル関数へと伝達される.第二引数のシグナル関数は必ずイベントシグナルを入力シグナルとして動くものとして定義する必要がある.シグナルの動かし方はpush, pull, push-pullの三種類を提供している.そのうち上記のmakeWAはpush-pull形式の動かし方に則っている.pullを含む形式で動かした場合はシグナルが発生していない場合でもシグナル関数の再計算が行われる.このときシグナル関数の入力としてNoEventが与えられる.第三引数の関数はシグナル関数を動かして得られた出力シグナルを引数に取る.その符られた値に応じてGUIの視覚的な更新を行わせる働きを持つ.このシグナル関数実行後に呼び出す事後処理用関数をシンク関数と呼ぶ. 4.3.4入出力シグナル用ウィジェットアクションmakeWA関数でウィジェットアクションをまとめ上げるには、シグナル関数の他に入力シグナルを作るイベントウィジェットアクションと出力シグナルを使って視覚面を整えるウィジェットアクションが必要となる.  Yeooeyではこれらのウィジェットアクションを作るための関数を各種用意している.以下にその例を示す. setSigOnActionEWA :: sig -> EvWidAct sig setTextWA :: ShowText a => a -> WidActイベントウィジェットアクションは関数名の末尾にEWA,ウィジェットアクションは関数名の末尾にWAをつけて区別している. setSigOnActionEWA関数はボタンなどのアクションが外部から行われた際にシグナルを発生させるイベントウィジェットアクションである.また,setTextWA関数は引数で受け取ったStringに変換できる値を反映させるウィジェットアクションである.
4.3.5   Neooeyとの比較
WidgetAction型もNeooeyには導入されていたが,いくつか違いがある.そのうちの一つはUIユニットと同じように型引数を持つか否かにある.Neooeyの方はWidgetAction型についてもウィジェット構造に沿った型引数を伴うため,探索や統一的な処理に限界があったがYeooeyではそれが可能になっている.また,ウィジェットアクションの実装方法についてはNeooeyとYeooeyで大きく異なる.Neooeyではウィジェットアクション内部で動的に変化する値を管理し,push方式のFRPに基づくアクションを構築することができた.その一方でpull方式で動かすことはできず,自動で盤面を再計算するようなGUIについては実装がややこしく苦手としていた.その点Yeooeyではウィジェットアクションの実装に Yampa由来のシグナル関数を導入することで, push形式でもpull形式でも動かすことを可能とした.それにより自動で再計算されるGUIについてもシグナル関数さえあれば容易に実装できるようになった．さらには内部計算を行うシグナル関数部分とGUIの入出力に関与するウィジェットアクションとが明確に分離されていることで値の流れを迫いやすくなっている.
4.4 expand関数
Yeooeyにおいて,UIユニットやウィジェットアクションを結合する際はウィジェット構造が一致している必要がある.もし一致していない場合は特殊な関数を通してウィジェット構造を調整する必要がある.ウィジェット構造の調整はUIユニット側で行う場合とウィジェットアクション側で行う場合の二種類存在する. UIユニット側で行う場合はウィジェットアクションと連携させる必要のないウィジェットを切り捨ててウィジェット構造を簡潔にする目的で使用する.また,ウィジェットアクション側で行う場合は保有するウィジェット構造の範囲を拡張する目的で使用する.これらの拡張関数は型クラスExpandableWidgetStructureのメソッドとして提供しており,それによってUIユニット,ウィジェットアクション,イベントウィジェットアクションのいずれにおいても適用させることが可能である.その拡張関数の一例は次の通りである.ただしウィジェットの木構造の二つの枝を伴う節に対して使用するもののみをここでは取り扱う. left, right :: ExpandableWidgetStructure wa => Unop wa (<=>) :: (Monad wa, ExpandableWidgetStructure (wa a)) => wa a -> wa a -> wa ()これらはUIユニットに適用する場合とウィジェットアクション,及びイベントウィジェットアクションに適用する場合で仕様がいささか異なる. UIユニットに対してはleft及びright関数を使用することができる.名前が示す方向のウィジェット構造を残し,そうでない方のウィジェット構造を除去する.ただし上下方向の結合に対しては上側のウィジェット構造のみを抜き出す場合はleft ,下側のウィジェット構造のみを抜き出す場合はrightを使用する.ウィジェットアクション,及びイベントウィジェットアクションに対してはleft , right , (<=>)を使用することができる. left, rightについては引数で与えられたアクションのウィジェット構造が関数名が示す方向の枝になるように拡張する．また，(<=>)は二つのウィジェットアクション,あるいはイベントウィジェットアクションを結合する.引数で受け取るアクションはウィジェット構造が一致している必要がなく,アクションの結合と同時に保有するウィジェット構造も結合される.
4.5 実装例：カウンター
本節では実際の例を通してYeooeyによるGUIの実装の過程やその例を紹介する．特にここではポタンクリックによってカウントアップが行われていくカウンターを実装する.ここで実装するGUIはポタンとテキストコントロールが一つずつあり,そのテキストコントロールには正の整数が記載されている.その数値はボタンが押された回数を意味しており,ボタンを一回押すたびにカウントが１ずつ増えていくというものである. YeooeyでGUIの実装を行う場合は入出力,UIユニット,シグナル関数,WAの順番で決定及び実装を行うことを推奨する.本節においてもその順番で紹介を行う.このカウンターの例の場合,入力はポタンが押されたことによるイベント,出力はテキストコントロールに表示させる文字列となる.この入出力はシグナル関数に対する入力シグナルと出力シグナルに直結する.そこで各シグナルに型を定めておくと見通しが立てやすくなる. type SigInp = Int -> Int type SigOup = Int入力シグナルを示す型SigInpはボタンが押されたときに伝達される情報の型を表す.今回はInt型の変換を行う関数を伝達させるようにし、シグナル関数内でそれをまとめ挙げる方式をとる.わかればいいのでInt型を与えている.また,出力シグナルを示す型SigOupは表示させる数値を扱うものとするため,Int型である. ここで,テキストコントロールは文字列の形で出力されるため,出力シグナルについてはString型として考えてもよい．どちらの定義で実装を行ってもシグナル関数実行後に呼び出すシンク関数によってつじつまを合わせることが可能である.次はUIユニットの定義である.ここでは思い描くGUIのイメージに合わせて小さなUIユニットを結合的に概築しつつ構成する.そのとき必要のないウィジェットを保持しないなど,保有するウィジェット構造についても気を遣っておく. counterUI :: UI counterUI = marginUI 10 $ countBoardUI <|> fillUI upButtonUI countBoardUI :: UI countBoardUI = minsizeUI (sz 200 40) $ textCtrlUI [text := "0"] upButtonUI :: UI upButtonUI = buttonUI [text := "up"]細かいレイアウト関数を適用してはいるが,本質だけを述べるとテキストコントロールウィジェットを作成するためにtextCtrlUI,ポタンウィジェットを作成するためにbuttonUIを呼び,それらを(<I>)演算子で縦方向に結合することで構成されている.このとき構築されるウィジェット構造は木構造の左枝にテキストコントロールウィジェットハンドラ,右枝にボタンハンドラが来る.次は処理の本体を記述するシグナル関数の実装である.入出力シグナルの型が合うように構築する. countSF :: SF (Event SigInp) SigOup countSF = accumHold 0 accumHold :: b -> SF (Event a) b accumHoldは内部で値を保有し，再計算の度にその値を出力するシグナル関数である.保有する値の初期値は引数として与えられ,入力イベントシグナルから受け取った関数でその都度更新及び出力が行われる.シグナル関数の実装が終わったらあとはそれを包み込むようにウィジェットアクションを構築する.この例ではボタンが押されるまで待機し，自動で再計算を行わせる必要がないため, push形式のFRPに基づいて実行する.このときmakePushWA関数を使用することでpush形式の実行が可能になる. wact :: WidAct wact = makePushWA evWA countSF sink sink :: SigOup -> WidAct sink = left.setTextWA evWA :: EvWidAct SigInp evWA = right setSigOnActionEWA (+1)今回の例ではポタンからの入力しか考えないため,イベントウィジェットアクションevWAは単一のイベントウィジェットアクションsetSigOnActionEWAで表現することができる.また,このポタンハンドラはウィジェット構造の右枝に位置することになるためrightEWA関数で拡張を行う.シンク関数についても唯一のテキストコントロールに対する処理しか考えないため、単一のウィジェットアクションsetTextWAで実装可能である.ただし、テキストコントロールハンドラはウィジェット構造の左枝に位置するのでleftWA関数で拡張を行う必要がある.これで一通り必要な部品の実装ができたのでrunYUI系の関数を使用してひとつにまとめれば完成である. main :: Action main = runFYUI counterUI counterWA Yeooeyの特徴の一つに拡張やUIユニット,ウィジェットアクションの差し替えがしやすい点が挙げられる.例えばUIユニットは変えずにテキストコントロール上で右クリックをするとカウントをリセットする機能を追加したい場合は0に変換するシグナルを伴うイベントウィジェットアクションを迫加してevWA2 :: EvWidAct SigInp evWA2 = setSigOnClickREWA (const $ const 0) <=> setSigOnActionEWA (+1)
とすることで達成される.
また,UIユニットも変更してカウントダウン用のポタンを追加することを考える.この場合は追加するベきウィジェットのUIユニットとイベントウィジェットアクション,及びそれぞれを結合したものを用意することで実装することができる. counterUI2 :: UI counterUI2 = marginUI 10 $ countBoardUI <|> buttonsUI buttonsUI :: UI buttonsUI = fillUI upButtonUI <-> fillUI downButtonUI downButtonUI :: UI downButtonUI = buttonUI [text := "down"] evWA2 :: EvWidAct SigInp evWA2 = setSigOnClickREWA (const $ const 0) <=> buttonsEWA buttonsEWA :: EvWidAct SigInp buttonsEWA = setSigOnActionEWA (+1) <=> setSigOnActionEWA (subtract 1)
4.6 実装例：ロケットランダー
前小節ではカウンターの小さなGUIの実装例を取り扱ったが，本節ではもう少し規模を大きくしてロケットランダーと呼ばれるゲームを制作する．尚，この例はHungerによる関数型言語Elmで記述されたソースコード†²をYeooeyで動くように移植したものとなっている.ロケットランダーはロケットを操作してプラットホームに着地させるゲームである.安全に着陸させることができればゲームクリアであり、地形に衝突してしまうとゲームオーバーとなる.この例はユーザからのキー入力を待つためpush的な側面を持つと同時にゲーム内時刻を経過させて定期的に盤面を更新する必要があるため,pull的な側面も持つ例となっている.実装方式はカウンターの例と同様にシグナルの型,UI,ウィジェットアクションの順で考察を行う.まず考えるのはシグナルに関してである.シグナル関数はゲームの状態を内部に保有し，入力シグナルで送られるイベントに応じて更新を行う.今回の例ではユーザのキーボード入力を入力シグナルとして考える．どのキーが押されたか，あるいは離されたかを捕提するように設計する.また,出力シグナルはシグナル関数がその時保持している内部状態を出力させる.内部状態は盤面を表現するのに必要なロケットや地形,ゲームの実行状態を持ち,シンク関数を用いて盤面の更新を行う.入力シグナルの型SigInp及び出力シグナルの型SigOupについては次のように定義される. data SigInp = KeyDown Key | KeyUp Key | NoOp data SigOup = Model { gravity :: Gravity , ship :: Ship , state :: GameState , platformPos :: PlatformPos , height :: Height , width :: Widthみシグナルの考察が終わったら,次はUIユニットの実装である.今回の例では描画用のスクリーンウィジェットを一つだけ配置するシンプルな形をとる.そしてスクリーン用のウィジェットの上でキーの入力を感知するように設計する. landerUI :: UI landerUI = minsizeUI (sz 3000 2000) $ panelUI []次はウィジェットアクションの実装である.そのために入力シグナルを生成するためのイベントウィジェットアクション,内部状態の更新を記述するシグナル関数，その都度状態を盤面に反映させるためのシンク関数を定める.
$ KeyDown .fromEvKey landerSF :: SF (Event SigInp) SigOup paintSink :: SigOup -> WidActイベントウィジェットアクションlanderEWAは先ほどの考察通り，キーが押された場合と離された場合に応じたシグナルを発生させる.シグナル関数landerSFとシンク関数paintSinkについては詳しい定義を省略するが、シグナル関数landerSFは入力シグナルやゲームの状態に応じた場合分け、シンク関数paintSinkはゲームの状態に応じた場合分けを意識することで比較的容易に実装することができる.最後に実装したUIユニットとウィジェットアクションをまとめて実行を行う. runNSYUI関数はタイトルバーに表示させる文字列とウィンドウサイズを迫加で指定することができる実行関数である. main = runNSYUI

5  関連研究
関数型言語を用いて設計されたGUIライプラリは多く存在する. しかしその多くはwxWidgetsやGtk+などのC系の言語で設計されたライブラリを少なからず活用しており, GUIの実装スタイルも逐次実行的な形をとるものがほとんどである.関数型言語でGUIの内部処理を記述するため、FRPを活用するライプラリも増えてきているがそれらは特にHaskellにおいて活発に開発されている. reactive-banana  [6]はApfelmusによって開発されたFRPライプラリである。できるだけシンプルに記述できるよう目指して現在も開発が続けられている.このライプラリは単独で使用するというよりもwxHaskellやGtk2Hsなどの他のGUIライプラリと組み合わせて使用することを想定しており,一般のGUIライプラリにFRPの性質を持たせることができる.構造としては主にFRPの概念を確立したElliottの論文[5], [4] に影響を受けており,時間によって変化する値をBehavior時間のある一点で発生する値をEventとしてこれらを結合,変換させることで処理を記述していく.例えばwxHaskellとreactive-bananaを組み合わせてGUIを作成する場合,まずwxHaskellによる記法でウィジェットを作成し、レイアウトや簡単なイベントなどの設定を行い,そのあとでreactive-bananaを活用した内部処理を記述していくことになる.ウィジェットハンドラについてはwxHaskellによるウィジェット関数によって生成された生のものを流用する形になる.  wxHaskellによるGUIアクション内で内部処理を記述するため, GUIアクション内部での分離は行われているが,アクション単位での分離は通常行われていない.  Yeooeyの場合はUIユニットでレイアウトやウィジェットハンドラの構造を設計し、ウィジェットアクションで内部処理を記述するように関数単位で分離しており,ウィジェットハンドラについてはUIユニットやWidAct型の内部に秘匿して直接見えないように設計している.ただし,reactive-bananaが提供する領域はあくまでもGUIの内部処理面だけなので,他のGUIライプラリと組み合わせることによって関数単位での分離は可能になる．reactive-bananaの領域はYeooeyにおけるウィジェットアクションを作成する際に実装するYampaのシグナル関数部分に対応している. FRPに注目が集まっている一方でFRPの考え方から脱却しようとする動きも存在する. Elm [2]はCzaplickiによって開発された関数型のドメイン固有言語である. FacebookかOpen Source Softwareとして公開しているJava ScriptのUIライプラリReact.js [11]などに影響を受けており, ElmはJavaScriptにコンバイルを行いウェプサイトやウェプアプリケーションを生成するツールとして使用する.また,設計に関しては強い型付けを持つ関数型言語にも影響を受けており,Haskellと類似した概文も多く見られる.実装に関してはFRPの考え方を使用していたが、より簡潔に記述することを迫究した結果,現在のパージョンではFRPを使用しないものとなっている. ElmにはElmアーキテクチャという実装スタイルが確立されており,このスタイルに沿ったコーディングを推奨している. ElmアーキテクチャによるとElmコードはmodel,message,update,viewの順に設計を行うとよいとされる.  modelはイベントの発生に応じて変化していく値のデータ型を表す. messageは発生させるイベントのデータ型を表す. updateは発生したイベントに応じたmodelの変換を定める関数である。viewは画面のレイアウトやウィジェットに対するイベントの設定を行う関数である.特にviewはHTML5の構文に沿って階層的に記述できるように設計されている. Elmアーキテクチャの役割ごとに別々に記述するスタイルはYeooeyの実装スタイルと通じる所がある.比較するとUIユニットがviewのレイアウト部分,ウィジェットアクションがupdateに対応する.またウィジェットアクション内部で動かすシグナル関数の入力シグナルの型がmessage,出力シグナルの型がmodelに対応している.
6結論と展望
レイアウトと内部処理を分けて実装する特徴をNeooeyから引きつぐことでGUIを結合的に構築できる性質を保持した.その上でそれぞれの型を再考して型引数をとらない形にしたことで視覚的な煩雑さがなくなり,またリスト処理などの柔軟な処理が行えるようになった.また,Yampaを導入し,シグナル関数ベースでウィジェットアクションを実装することでpush-pullなFRPに基づく処理が可能となり，簡潔に表現できるGUIの幅が広がった.さらにイベントウィジェットアクションを導入することで入力シグナルの伝達を表面から隠すことでユーザーによる不注意や想定外によるミスを起こしにくくし,可読性を高めることができた.現状のYeooeyではUIユニットやウィジェットアクションの型が聖引数をとらなくなったことでウィジェット構造をユーザーが管理する必要があり，それに従ってexpand系関数を適用する必要がある．これは小規模なGUIではさほど苦痛ではないが、大規模な例になればなるほど構造を常に把握しなくてはならない.この問題を緩和,あるいは解消に努めていきたい.尚,本論文で紹介したNeooeyのソースコード及びGUIの実装例についてはウェプサイト上で公開†³し
ている.
謝辞本論文の内容について議論していただいた桜井貴文氏(千葉大)に感謝する.