Archive for June 13th, 2006

ポイントフリースタイル

Posted by 向井 淳 on Tuesday, 13 June, 2006
>2つのリストが等しいかどうかを比較したい。ただし、一方が他方より短い場合、短い方に合わせるとする。 “abcdef” == “abc” が等しいようなイメージ。こういうケースは結構あるんじゃないかと思う。 > >どうするかっていうといろいろやり方がある気がするが、単純には zipWith で作れる。なかなか面白い。 > >listEquals l1 l2 = and $ zipWith (==) l1 l2 > >さて、これをポイントフリースタイルで書きたい。つまり、引数の l1 とか l2 とかを消したい。 > >これが引数ひとつの場合は簡単だ。関数合成演算子の (.) があって、 > >f . g = \a -> f (g a) > >になっているからこれを使う。上のように二引数だとどうだろう。実際に考えてみよう。まず、 > >c2 f g = \a1 a2 -> f $ g a1 a2 > >と書くところから始めよう。 > >この定義から、まず a2 を削れることがわかるだろう。 > >c2 f g = \a1 -> f . g a1 > >ここからがちょっとポイントだが、これは次のように書ける。 > >c2 f g = \a1 -> (f.) $ g a1 > >この (f.) はセクションである。とすると、 a1 も追い出せる。 > >c2 f g = (f.) . g > >うむ。 > >したがって元の話題に戻ると、 > >listEquals = (and.) . zipWith (==) > >である。美しい。 > >さらにさらに。 g が3引数関数だとどうなるか。 > >c3 f g = \a1 a2 a3 -> f $ g a1 a2 a3 > >これは、 > >c3 f g = \a1 a2 -> f . g a1 a2 > >ゆえに > >c3 f g = \a1 a2 -> (f.) $ g a1 a2 > >であるから、 c2 を使って、 > >c3 f g = c2 (f.) g = ((f.).) . g > >となる。 > >これを繰り返せば、任意の引数の数に対して、 (.) を使ってポイントフリースタイルで書けそうだ。逆に言うと、この形式のものは、 g が複数引数であるような関数合成であるということか。 > >ではつぎに、 f も引数を取るケース。 > >cb f g = \a1 a2 -> f a1 (g a2)
= \a1 -> f a1 . g
> >ここまでは簡単だが、これはちょっと面倒くさい。 a1 を右側に持ってきたい。 flip が使えそうだ。 > >= \a1 -> flip (.) g $ f a1
= flip (.) g . f
> >では f が2引数だとどうなるか。 > >cb2 f g = \a1 a2 a3 -> f a1 a2 (g a3)
= \a1 a2 -> f a1 a2 . g
= \a1 a2 -> flip (.) g $ f a1 a2
= \a1 -> flip (.) g . f a1
= (flip (.) g .) . f
> >んじゃさらに、 f の第一引数に関数 g が適用され、第二引数には関数 h が適用されるような場合。 > >branch f g h = \a1 a2 -> f (g a1) (h a2)
= \a1 -> f (g a1) . h
= \a1 -> flip (.) h $ f (g a1)
= \a1 -> flip (.) h $ (f . g) a1
= flip (.) h . (f . g)
> >f も引数欲しい。 > >branch' f g h = \a1 a2 a3 -> f a1 (g a2) (h a3)
= \a1 a2 -> f a1 (g a2) . h
= \a1 a2 -> flip (.) h $ f a1 (g a2)
= \a1 a2 -> flip (.) h $ (f a1 . g) a2
= \a1 -> flip (.) h . (f a1 . g)
= \a1 -> flip (.) h . (flip (.) g (f a1))
= \a1 -> flip (.) h . (flip (.) g . f) a1
= (flip (.) h .) . (flip (.) g . f)
> >また、 h が2引数関数の場合。 > >branch'' f g h = \a1 a2 a3 -> f (g a1) (h a2 a3)
= \a1 -> (f (g a1) .) . h
= \a1 -> ((f . g) a1 .) . h
= \a1 -> flip (.) h ((f . g) a1 .)
= \a1 -> flip (.) h ((.) ((f . g) a1))
= \a1 -> flip (.) h (((.) . (f . g)) a1)
= flip (.) h . ((.) . (f . g))
> > >まとめ >: セクションに (.) を利用すると簡単に難読化できます。ポイントフリースタイルは、用法、用量を守って使いましょう。 > >……ところでこれ、合ってるのかな……。 >

concurrent haskell

Posted by 向井 淳 on Tuesday, 13 June, 2006
>Concurrent Haskell の動作をチェックしてみた。まず、いちばん基本となる関数が forkIO と forkOS で、どちらも IO () -> IO ThreadId の型を持つ。違いは、 forkIO は仮想機械レベルの並列計算なのに対して、 forkOS は OS のネイティブスレッドを使う。そのため、 forkOS の方は ghc を -threaded でコンパイルしないと動かないといった制約がある(runhaskell や ghci では動作しないようになっている)。そういった差はあるが、下記のレベルではどっちでも差はない。 > >というわけで、まずは動作例。 > >do forkOS (mapM_ (putStrLn "child") [1..10])
mapM_ (putStrLn "parent") [1..10]
> >こいつは、子スレッドが10回「child」と書き出し、同時並列的に親が「parent」と書き出す。これは悪くないのだが、親は子の終了を待たないので、実際にこれをやると、 child がぜんぶ出力される保証がない(forkIO を使って対話環境で実行すると、親スレッドが終了したところでプロンプトに戻ってしまい、プロンプトで何かやると、次の計算に、前の子スレッドの実行が混ざるようになる)。 > >これじゃ困るのでスレッドの終了を待ちたい。ただ、 pthread の API と違って、 fork* の返り値である ThreadId はその目的には使えない。これには MVar を使う。 MVar は、「変数の置き場」みたいなもので、 IORef に似ているが、「何もない」状態にすることができる(IORef (Maybe a) に似てるか)。 MVar を読むと変数が「取られ」空になったりする。もし空なら、そのスレッドの動作はブロックするから、これを使えば子スレッドの終了を待つことができる。 > >do v <- newEmptyMVar
forkOS (mapM_ (putStrLn.("child "++).show) [1..10] >> putMVar v 0)
mapM_ (putStrLn.("parent "++).show) [1..10]
takeMVar v
> >newEmptyMVar は、空っぽの MVar を作る(初期値を指定する newMVar などもある)。 MVar に値を「置く」のが putMVar で、読み込むのが takeMVar だ。実際には、読み込むが値を「取る」ことをしない readMVar というのもある。 > >さて、こうやってスレッド間で通信をするわけだが、 MVar は1つの要素しか持たないという制約があるから、新しい値を置こうとするとブロックしたり、古い値を消すハメになったり、といった問題がある。なので、どんどん値を追加していく、いわばチャンネルが欲しいこともある。そのためのものが Chan だ。 > >do ch <- newChan
forkOS (writeList2Chan ch [1..100])
loop ch
where loop ch = isEmptyChan ch >>= flip unless (readChan ch >>= print >> loop ch)
> >Chan にも MVar と同じような API が用意されているが、特徴的なのは writeList2Chan というのがあることか。リストをまるごとチャンネルに書き出す処理を行っている。どうでもいいが、このサンプルで writeList2Chan を forkOS する意味はあんまりない。 > >ここで、1つのチャンネルを複数のスレッドから読み込むことを考える。同じものを読み込むわけだから、一方のスレッドで読むと、もう一方のスレッドでは、その次の要素が読まれる。つまり、値を奪いあうわけだ。だから、こうなる。 > >do v <- newEmtpyMVar
ch <- newChan
forkOS (writeList2Chan ch [1..10])
forkOS (loop "child" ch >> putMVar v 0)
loop "parent" ch
takeMVar v
where loop suf ch = isEmptyChan ch >>= flip unless (readChan ch >>= putStrLn.(suf++).show >> loop suf ch)
> >parent と child が交互にあらわれるが、数値そのものは1〜10が1度ずつしか出ないことがわかるだろう。 > >さて、このままだと、複数のスレッドに同時に値を伝えるようなブロードキャストをするのがメンドくさい。この目的のため、チャンネルを複製することができる。 > >do v <- newEmptyMVar
ch <- newChan
ch' <- dupChan ch
forkOS (writeList2Chan ch [1..10])
forkOS (loop "child" ch' >> putMVar v 0)
loop "parent" ch
takeMVar v
(loop の定義は略)
> >child と parent のそれぞれが 1〜10を出力することがわかるだろう。 dupChan は空のチャンネルが作る。しかし、作られた新しいチャンネルともとのチャンネル、どちらかに書き込まれたデータはどちらのチャンネルからも読めるようになる。書き込み側では ch に書き込んでいるが、 ch' からもデータを読めるようになるわけだ。 > >ところで、 writeList2Chan は最近のマシンでは一瞬で終わってしまうので、 > >forkOS (writeList2Chan ch [1..10])
ch' <- dupChan ch
> >と順序を変えると、 dupChan で複製する前に書き込みが完了してしまって、 ch' は空のチャンネルになってしまい、 child は何も出力しない。 > >そういえば Control.Concurrent.STM というのもあるのだった。こちらは未見。 >

NEWスーパーマリオブラザーズ

Posted by 向井 淳 on Tuesday, 13 June, 2006
>オールクリア! 正直、ラスト近くはしんどかった。コインも8割方集めるくらいまではパズルっぽい楽しさがあるのだが、最後の取りこぼしはマメマリオとかカメとかにならないと取れないとかいった、ほとんど苦痛なやつばっかりなので。まあ、総合ではよくできたゲームだと思いますが。 >

Google Earth on Linux

Posted by 向井 淳 on Tuesday, 13 June, 2006
> >http://earth.google.com/download-earth.html >。おお。 > >……と思ってダウンロード→インストールしたのだが、スプラッシュが出たところで止まって動きません。何が悪いんだ。 >