マニピュレータ
最初にお断りしておくのだが、今回の文章は
Cマガ2004年9月号(つまり今書店に行ったら売ってあるやつ)の
「επιστημηのオブジェクト工房」の記事をかなり参考にしたので
そちらをお持ちなら先に読んでもらえるとよいかもしれない。
まぁ、話題はiostreamに対するマニピュレータの定義をしやすくする
"マクロ"あるいは"テンプレート"というようなことなのだが、
(こんなこと書いていいんかな…)
で、その一節に
…メカニズム自体は関数オブジェクトにも通じていて
なかなかにプログラマゴコロを刺激するのではないだろうか…云々
ということが記載されていて、
関数オブジェクトもとい、ファーストクラス関数な言語では
そんなことは直截的なんだ…といつものごとく考えてみたり、何とかしたり。
ここではマニピュレータとはCマガと同じように、
streamに何らかの作用を及ぼすもの、という意味で用いている。
cout<
とかのendlとかになるのかな。
そこで問題となるのが自分でマニピュレータを作る場合。
星(アスタリスク)をn個表示するようなマニピュレータ、
cout<
のようなものを作りたいと。
私の第一感ではoperator<<を定義したクラスをつくるのかなあ、
て感じだったのだが、ここは星n個から成る文字列を返す解法が書いてあった。
おそらく、正格性評価を考慮したときの使用スペースの問題を度外視すると
問題をoperator<<(ostream &,string&)に還元する解法のほうが
クラスを定義するよりも遥かに綺麗なやり方だと思う。
Haskellだと遅延評価なのでなおのこと。
そういうことで、文字列をストリームに突っ込むだけのものならば
文字列に還元でいいのではなかろうかと一人納得する中、
話はクラスを定義する方向へ。
ostream &foo(ostream &os,...){ } OAPP(...) manip(foo);
昔C++のライブラリにあったiomanipを使えばこのようにostreamに対して
任意の操作を行うことが出来るマニピュレータが作れると。
その実はOAPPマクロがこっそりとクラスを定義しているらしい。
Cマガのほうではその後templateを使ったiomanipの再実装
みたいなことになっていくのだが、ここではHaskellでの実装を
行ってみようかと思う。関数型の考察、あるいはちょっとだけ<<オペレータが
懐かしかったのかもしれない。
まず、<<をどうするか。<<に突っ込めるものをManipuratorクラスとしよう。
ManipulatorクラスがShowクラスから継承されていればデフォルトの
実装が作れるのでそのほうがよいかもしれない。
ということで、定義してみたのが以下のManipulatorクラス。
class Show m => Manipulator m where (<<) :: IO Handle -> m -> IO Handle a << m = do h <- a hPutStr h (show m) return h
何か色々めんどくさかったのでostreamの変わりは単にHandleで。
IOが付いてるのは都合上。付けないとコマンドが実行できないので。
これでShowクラスのインスタンスは何でも突っ込めるのだが、
明示的にinstanceしておかないといけないので、
instance Manipulator Int instance Manipulator Integer instance Manipulator Double
適当にこのあたりを追加。
Charに対してはshowして欲しくないので
instance Manipulator Char where a << m = do h <- a hPutChar h m return h
このような特殊化を施す。
さらに、リストに対して
instance (Manipulator a) => Manipulator [a] where a << m = foldl (<<) a m
このようにインスタンスを定義。
これで文字列、整数などは突っ込めるようになった。
また、<<が組み立てるのはIO Handleなコマンドなので、
cout = return stdout
このように定義しておく。
これで、
main = cout<<"Hello, World"<
のようなC++チックなことが出来るように。
で、前述のstarsのようなものがどう定義できるのか。
main = cout<
のように書いて星が5こ表示されればよい。
私が第一感で浮かばなかった手である文字列への還元、で考えると
stars = flip replicate '*'
これでよい。
例として上がっているclsやlocateも
cls = "\x1b[2J" locate x y = "\x1b["++show x++";"++show y++"H"
ありゃ、出来ちゃった…
でもまぁ、やっぱりあれなので、
文字列では収まらないとき、バッファのフラッシュとかをするものとかを
作ろうと思うとHandleを触る必要が出てくるので、
そういうのを考えると任意のHandle操作を行うものをマニピュレータに
すると言うのはあながち無駄では無さそうな気もする。
で、そういうものを考えてみると、
まずひとつ浮かぶのが普通にデータ型+インスタンス化
という方法。
data Stars = Stars Int deriving (Show) instance Manipulator Stars where a << (Stars n) = do h <- a replicateM_ n (hPutChar h '*') return h main = cout<
これはC++でoperator<<を定義したクラスを定義するのに極めて近い。
Haskellならでは?…のカリー化を用いた方法も考えられる。
(<<<) :: IO Handle -> (Handle -> IO a) -> IO Handle a <<< f = do h <- a f h return h
新たに<<<というオペレータを定義し、
それの第二引数としてコマンドを受け取れるようにする。
すると、
starsf n h = replicateM_ n (hPutChar h '*')
星をhにn個表示する…といった普通の関数を
main = cout<<
このように使用することが出来るようになる。
この方法は個人的には良いんではないかなぁと思うのだが、
いかんせんオペレータを共通化できなかったのが痛い。
制限された関数だけクラスのインスタンスにすることが出来れば
(Handle -> IO a) の関数をManipulatorクラスにすることが出来るのだが、
出来ない…あるいはやり方がわからない。
というか、そもそもHaskellのクラスはそういうもんではないのか。
やはりまぁ、よく分からんというか、結局これだけ書いて文字列解が
一番よさげなのはどうしたものか。