ポイントフリースタイル入門
Haskell にはポイントフリースタイルというのがあります。
例えば
foo x = f (g x)
という中の x というのが「ポイント」と言うらしいです(型を明示していないから x の型が a->b だったりする可能性もあるけどその可能性は置いといて)。要するに値のことですね。
で、このポイントを除けてプログラミングするのをポイントフリースタイルと言います。
この場合、
foo = f.g
となります。
ということで、ありとあらゆるコードをポイントフリースタイルで書けるように訓練しましょう。
基本的に、書いてれば慣れるのでどんどん書きましょう。
基本
基本的に (.) 関数を使います。
foo x = f (g x) foo x = (f.g) x foo = f.g
です。
また、(.) は二項演算子なので、これを関数形式で書けば、
f.g = (.) f g
となり、更に
= (f.) g = (.g) f
と書くこともできます。
これは単に式を変形しただけなのですが、f (g x) の形式にするためにこれを結構使います。
あと、最初の例でもこっそりやっていますが、
foo x y = f x y
こんな関数があったとき、y は左と右で両方とも右端にあるので、取り除くことができます。
foo x = f x
で、さらに x も同様に取り除けます。
foo = f
こうなります。
これだけ知ってれば結構頑張れます。
問題1
ということでいくつか解きましょう。
foo x y = f (g x y)
まずは y を除けることから考えましょう。
g' = g x と考えれば簡単に分かります。
-- g x を g' とする foo x y = f (g' y) where g' = g x -- f (g x) の形になったので . に直す foo x = f.g' where g' = g x -- where の部分を取り除く foo x = f.g x
となります。
次に x を除けましょう。
これは
foo x = (f.) (g x)
であると考えればすぐ分かります。
-- (f.) を f' とする foo x = f' (g x) where f' = (f.) -- f (g x) の形になったので . に直す foo = f'.g where f' = (f.) -- where の部分を取り除く foo = (f.).g
こうなります。
つまり
foo x y = f (g x y)
をポイントフリーにすると
foo = (f.).g
になります。
問題2
もうひとつぐらいやっておきましょう。
foo x y z = f x (g y z)
z から取り除きます。
-- f x を f' に、g y を g' にする foo x y z = f' (g' z) where f' = f x g' = g y -- f (g x) の形になったので . に直す foo x y = f'.g' where f' = f x g' = g y -- where の部分を元に戻す foo x y = f x.g y
次に y です。
-- 式を変形する foo x y = (f x.) (g y) -- (f x.) を f' とする foo x y = f' (g y) where f' = (f x.) -- . にする foo x = f'.g where f' = (f x.) -- where を戻す foo x = (f x.).g
最後に x を除けます。これはちょっと強敵かもしれません。
-- (f x.) を f' とする foo x = f'.g where f' = (f x.) -- 式を変形する foo x = (.g) f' where f' = (f x.) -- (f x.) を式変形する foo x = (.g) f' where f' = (.) (f x) -- f' は引数を取るようにしてみましょう foo x = (.g) (f' x) where f' x = (.) (f x) -- f' が f (g x) の形になっているので . に直す foo x = (.g) (f' x) where f' = (.).f -- foo が f (g x) の形になっているので . に直す foo = (.g).f' where f' = (.).f -- where を戻す foo = (.g).((.).f) -- . 演算子は右結合なので無駄な括弧を取り除く foo = (.g).(.).f
となります。
flip 登場
ドット関数さえあれば結構頑張れるのですが、
foo x y = f y x
みたいなのになると厳しいので、flip 関数を使うことにします。
foo x y = flip f x y
となり、
foo = flip f
になります。
問題3
flip を使って自由に引数の順番を入れ替えられるようになっているといい感じです。
foo x y z = f z y x -- f を flip して z を後ろに移動させる foo x y z = flip f y z x -- flip f y を flip して z を後ろに移動させる foo x y z = flip (flip f y) x z -- z を取り除く foo x y = flip (flip f y) x -- flip f y を y' とする foo x y = flip y' x where y' = flip f y -- y' を後ろにやるために flip する foo x y = flip flip x y' where y' = flip f y -- where を戻す foo x y = flip flip x (flip f y) -- (flip flip x) を f' に、(flip f) を g' に置き換えると f' (g' y) になるので . に直せる foo x = flip flip x.flip f -- あとは普通にポイントフリーにする foo x = (.flip f) (flip flip x) foo = (.flip f).flip flip
頑張ってみた
練習として、4引数の関数の順番を全パターン網羅してみました。
data A = A data B = B data C = C data D = D test :: A -> B -> C -> D -> a test = undefined test1 :: A -> B -> D -> C -> a test1 = (flip.).test test2 :: A -> C -> B -> D -> a test2 = flip.test test3 :: A -> C -> D -> B -> a test3 = (flip.).flip.test test4 :: A -> D -> B -> C -> a test4 = (.flip flip).flip (.).test test5 :: A -> D -> C -> B -> a test5 = (.flip flip).flip (.).flip.test test6 :: B -> A -> C -> D -> a test6 = flip test test7 :: B -> A -> D -> C -> a test7 = (flip.).flip test test8 :: B -> C -> A -> D -> a test8 = flip.flip test test9 :: B -> C -> D -> A -> a test9 = (flip.).flip.flip test test10 :: B -> D -> A -> C -> a test10 = (.flip flip).flip (.).flip test test11 :: B -> D -> C -> A -> a test11 = (.flip flip).flip (.).flip.flip test test12 :: C -> A -> B -> D -> a test12 = (.test).flip flip test13 :: C -> A -> D -> B -> a test13 = (flip.).(.test).flip flip test14 :: C -> B -> A -> D -> a test14 = (.flip test).flip flip test15 :: C -> B -> D -> A -> a test15 = (flip.).(.flip test).flip flip test16 :: C -> D -> A -> B -> a test16 = (.flip flip).flip (.).(.test).flip flip test17 :: C -> D -> B -> A -> a test17 = (.flip flip).flip (.).(.flip test).flip flip test18 :: D -> A -> B -> C -> a test18 = (.test).(.).flip flip test19 :: D -> A -> C -> B -> a test19 = (.flip.test).(.).flip flip test20 :: D -> B -> A -> C -> a test20 = (.flip test).(.).flip flip test21 :: D -> B -> C -> A -> a test21 = (.flip.flip test).(.).flip flip test22 :: D -> C -> A -> B -> a test22 = (.(.test).flip flip).(.).flip flip test23 :: D -> C -> B -> A -> a test23 = (.(.flip test).flip flip).(.).flip flip main = putStrLn ""
どれも半分機械になってひたすら変形しまくりました。
もっと綺麗に簡約できるのもあるのかもしれないですけど、機械的にやるとこんな感じです。
全部説明するのは辛いので、長い test17 だけ変形の様子を見てみましょう。
test17 :: C -> D -> B -> A -> a test17 c d b a = test a b c d -- a test17 c d b a = flip test b a c d test17 c d b a = flip (flip test b) c a d test17 c d b a = flip (flip (flip test b) c) d a test17 c d b = flip (flip (flip test b) c) d -- b test17 c d b = flip flip d (flip (flip test b) c) test17 c d b = flip flip d (flip flip c (flip test b)) test17 c d b = f' (g' b) where f' = flip flip d g' b = flip flip c (flip test b) test17 c d = f'.g' where f' = flip flip d g' = flip flip c.flip test test17 c d = flip flip d.flip flip c.flip test -- d test17 c d = (.flip flip c.flip test) (flip flip d) test17 c = (.flip flip c.flip test).flip flip -- c test17 c = (.flip flip) (.flip flip c.flip test) test17 c = (.flip flip) (flip (.) (flip flip c.flip test)) test17 c = (.flip flip) (flip (.) ((.flip test) (flip flip c))) test17 c = f' (g' c) where f' = (.flip flip) g' c = flip (.) ((.flip test) (flip flip c)) test17 c = f' (g' c) where f' = (.flip flip) g' c = f'' (g'' c) where f'' = flip (.) g'' c = (.flip test) (flip flip c) test17 = f'.g' where f' = (.flip flip) g' = f''.g'' where f'' = flip (.) g'' = (.flip test).flip flip test17 = (.flip flip).flip (.).(.flip test).flip flip
となります。
まとめ
これだけ変形しておきながら、f や g といった関数の具体的な意味については一切言及してません。
つまりポイントフリーにするにあたって、関数の具体的な処理なんて知る必要はありません。
それぞれの関数の意味が分からなくても、とりあえずポイントフリーにすることはできます。
ということで頑張ってどんどんポイントさんを自由にしてあげましょう。
Q.なぜポイントフリースタイルで書くのですか?
A.そこにポイントがあるから