2014/11/17

Haskellのフクロウ ((.)$(.))

 Haskellのポイントフリー記法により, いくつか新発見があったようなのですが, そのうちの一つがHaskellのフクロウ・コンビネータなのだそうで.


 次のコンビネータがそれなのですが, たしかに見ようによってはフクロウの顔に見えなくもないです.
*Main> :t ((.)$(.))
((.)$(.)) :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c
 で, 試しに型を見てみると次のような感じ.
*Main> :t ((.)$(.))
((.)$(.)) :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c
*Main> :t ((.)(.))
((.)(.)) :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c
*Main> :t (.)(.)
(.)(.) :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c
 ドルマークはgroup expressionと呼ばれ, 括弧と同等のもので, $から行末, またはその前の閉じ括弧の一つ手前までを括弧でくくる演算子です. なので, フクロウコンビネータのドル記号は実は不要(あってもなくても意味は同じ)で, さらに, 外側を囲っている括弧も(上記の例では)不要(Haskellでは, applyの優先順位が一番高いため)なので, 最終的に「フクロウ」は「見下ろす目玉」まで簡略化できました. ドット記号は, 関数合成の演算子なので, これ以上小さくすることはできません.
 しかし, こんなコンビネータどこで使うんだと思っていたのですが, pointfulに書くと,
f a b c d = a b (c d)
なのだそうで, 意外と使えるかもしれないですね. mapでは引数を二つとりますが, たいてい, 二番目の引数は, ごちゃごちゃしていますから.
*Owl> ((.)$(.)) map (* 2) reverse [1..10]
[20,18,16,14,12,10,8,6,4,2]
のように使えます.

 フクロウ・コンビネータは意味的には((.)(.))の目玉二つに相当するわけですが目玉の数を増やすと様々なコンビネータが作れます.
((.))                   :: (b -> c) -> (a -> b) -> a -> c
((.)(.))                :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c
((.)(.)(.))             :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
((.)(.)(.)(.))          :: (a -> a1 -> b -> c) -> a -> a1 -> (a2 -> b) -> a2 -> c
((.)(.)(.)(.)(.))       :: (b1 -> c) -> (b -> b1) -> (a -> b) -> a -> c
((.)(.)(.)(.)(.)(.))    :: (b -> b1 -> c) -> (a -> b) -> a -> (a1 -> b1) -> a1 -> c
((.)(.)(.)(.)(.)(.)(.)) :: (a -> b -> c) -> a -> (a1 -> a2 -> b) -> a1 -> a2 -> c
((.)(.)(.)(.)(.)(.)(.)(.)):: (b -> c) -> (a -> a1 -> a2 -> b) -> a -> a1 -> a2 -> c
目玉(合成関数)の数を増やしても, 生成される関数の型に一貫性がないように見えます.
 replで試してみると以下のようになります. 二番目はフクロウ・コンビネータの使い方と同じです.
*Owl> ((.)) reverse tail [1..10]
[10,9,8,7,6,5,4,3,2]
*Owl> ((.)(.)) map (* 2) reverse [1..10]
[20,18,16,14,12,10,8,6,4,2]
*Owl> ((.)(.)(.)) reverse map (* 2) [1..10]
[20,18,16,14,12,10,8,6,4,2]
*Owl> ((.)(.)(.)(.)) foldl (+) 0 tail [1..10]
54
*Owl> ((.)(.)(.)(.)(.)) reverse reverse reverse [1..10]
[10,9,8,7,6,5,4,3,2,1]
*Owl> ((.)(.)(.)(.)(.)(.)) map (+) 2 reverse [1..10]
[12,11,10,9,8,7,6,5,4,3]
 こんな感じで5つ「目」までは使えそうです. 使う関数の名前と組み合わせによっては, 括弧で直接囲うよりも見やすくなるかもしれません. とはいえ, 素直に書いた方が手っ取り早くて安全なのは言うまでもないですが.
 mapとfilterを繰り返し使いたい時と, foldlで組み合わせたい時は, 次のように書けます.

*Owl> ((.)$(.).(.)) map (+ 2) filter (< 3) [1..10]
[3,4]
*Owl> ((.)$(.)$(.).(.)) foldl (+) 0 map (* 3) [1..10]
165

0 件のコメント :