ようこそ。睡眠不足なプログラマのチラ裏です。

Haskellのお勉強 その11

前回勉強したflip関数についてググってたら、面白そうなのを見つけた。
こりゃ、関数合成やら部分適用やらポイントフリースタイルの練習教材としてもってこいだ。


3引数のflipをポイントフリースタイルで書こう

元ネタの元ネタ
haskellのある暮らし - 頭の体操:3引数flip

元ネタ
Haskellはスケるよ - 3引数のflip


以下のような3つの引数をとるflip関数を、ポイントフリースタイルで書け。という問題。
fは別になくさなくてもおkとのこと。

flip31 :: (a -> b -> c -> d) -> a -> c -> b -> d
flip31 f a c b = f a b c

flip32 :: (a -> b -> c -> d) -> b -> a -> c -> d
flip32 f b a c = f a b c

flip33 :: (a -> b -> c -> d) -> b -> c -> a -> d
flip33 f b c a = f a b c

flip34 :: (a -> b -> c -> d) -> c -> a -> b -> d
flip34 f c a b = f a b c

flip35 :: (a -> b -> c -> d) -> c -> b -> a -> d
flip35 f c b a = f a b c


とりあえず、そのまま動かしてみる。

main = do putStrLn  $ flip31 f 'a' 'c' 'b'
          putStrLn  $ flip32 f 'b' 'a' 'c'
          putStrLn  $ flip33 f 'b' 'c' 'a'
          putStrLn  $ flip34 f 'c' 'a' 'b'
          putStrLn  $ flip35 f 'c' 'b' 'a'

f :: Char -> Char -> Char -> String
f a b c = a:b:c:[]

flip31 :: (a -> b -> c -> d) -> a -> c -> b -> d
flip31 f a c b = f a b c

flip32 :: (a -> b -> c -> d) -> b -> a -> c -> d
flip32 f b a c = f a b c

flip33 :: (a -> b -> c -> d) -> b -> c -> a -> d
flip33 f b c a = f a b c

flip34 :: (a -> b -> c -> d) -> c -> a -> b -> d
flip34 f c a b = f a b c

flip35 :: (a -> b -> c -> d) -> c -> b -> a -> d
flip35 f c b a = f a b c


■実行結果

abc
abc
abc
abc
abc

お、ちゃんと機能していますね。


それでは早速、脳内でポイントフリースタイルに変換を・・、ちょ無理www
とりあえず地道に順序だててチマチマ崩して攻略しようと思います。
こいつはいい「脳トレ」になりそうです。


■その1

flip31 :: (a -> b -> c -> d) -> a -> c -> b -> d
flip31 f a c b = f a b c
              → (f a) b c       --部分適用する
              → flip (f a) c b   --flipする
              → flip.f a c b     --関数合成する
              → flip.f           --引数がほどけて\(^o^)/オワタ

{-これは簡単ですね-}


■その2

flip32 :: (a -> b -> c -> d) -> b -> a -> c -> d
flip32 f b a c = f a b c
              → (f a b) c        --部分適用する
              → (flip f b a) c   --flipする
              → flip f b a c     --部分適用をほどく
              → flip f           --引数がほどけて\(^o^)/オワタ

{-というか、こんなまどろっこしい事はせずに、
引数aとbの入れ替わりなので、はなから素直に関数fにflipすりゃ終わりです(;´Д`)-}


■その3

flip33 :: (a -> b -> c -> d) -> b -> c -> a -> d
flip33 f b c a = f a b c
              → (f a b) c              --部分適用する
              → (flip f b a) c         --flipする
              → (flip f b) a c         --部分適用をほどく
              → (flip (flip f b)) c a  --flipする
              → (flip (flip f) b) c a  --部分適用をほどく
              → (flip.(flip f) b) c a  --関数合成する
              → flip.(flip f) b c a    --部分適用をほどく
              → flip.(flip f)          --引数がほどけて\(^o^)/オワタ

{-というか、早い話flip32したあとflip31したら終わりじゃんってゆー・・・
つまるところ、「(flip31.flip32) f」でおkですね-}


■その4

flip34 :: (a -> b -> c -> d) -> c -> a -> b -> d
flip34 f c a b = f a b c
              → (f a) b c             --部分適用する
              → flip (f a) c b        --flipする
              → (flip.f) a c b        --関数合成する
              → ((flip.f) a c) b      --部分適用する
              → (flip (flip.f) c a) b --flipする
              → flip (flip.f) c a b   --部分適用をほどく
              → flip (flip.f)         --引数がほどけて\(^o^)/オワタ

{-というかこれも、早い話flip31にflipしたら終わりじゃんってゆー・・・
つまるところ、 「(flip.flip31) f)」でおkですね-}


■その5

flip35 :: (a -> b -> c -> d) -> c -> b -> a -> d
flip35 f c b a = f a b c
              → (f a) b c                    --部分適用する
              → flip (f a) c b               --flipする
              → (flip.f) a c b               --関数合成する
              → ((flip.f) a c) b             --部分適用する
              → (flip (flip.f) c a) b        --flipする
              → flip (flip.f) c a b          --部分適用をほどく
              → flip (flip (flip.f) c) b a   --部分適用する
              → flip.(flip (flip.f)) c b a   --関数合成する
              → flip.(flip (flip.f))         --引数がほどけて\(^o^)/オワタ

{-というかこれも、早い話flip33にflipしたら終わりじゃんってゆー・・・
つまるところ、 「(flip.flip33) f」でおkですね-}


ちゃんと動くのかこれ?ってことで、動作確認してみる。

main = do putStrLn  $ flip31 f 'a' 'c' 'b'
          putStrLn  $ flip32 f 'b' 'a' 'c'
          putStrLn  $ flip33 f 'b' 'c' 'a'
          putStrLn  $ flip34 f 'c' 'a' 'b'
          putStrLn  $ flip35 f 'c' 'b' 'a'

f :: Char -> Char -> Char -> String
f a b c = a:b:c:[]

flip31 :: (a -> b -> c -> d) -> a -> c -> b -> d
flip31 f = flip.f

flip32 :: (a -> b -> c -> d) -> b -> a -> c -> d
flip32 f = flip f

flip33 :: (a -> b -> c -> d) -> b -> c -> a -> d
flip33 f = (flip31.flip32) f

flip34 :: (a -> b -> c -> d) -> c -> a -> b -> d
flip34 f = (flip.flip31) f

flip35 :: (a -> b -> c -> d) -> c -> b -> a -> d
flip35 f = (flip.flip33) f

■実行結果

abc
abc
abc
abc
abc

お、動くじゃん。なんかすげーぞハスケルwww



更にfも取り除いて、完全ポイントフリースタイルへ・・・

flip31 :: (a -> b -> c -> d) -> a -> c -> b -> d
flip31 = (flip.)

flip32 :: (a -> b -> c -> d) -> b -> a -> c -> d
flip32 = flip

flip33 :: (a -> b -> c -> d) -> b -> c -> a -> d
flip33 = (flip31.flip32)

flip34 :: (a -> b -> c -> d) -> c -> a -> b -> d
flip34 = (flip.flip31)

flip35 :: (a -> b -> c -> d) -> c -> b -> a -> d
flip35 = (flip.flip33)

そして、更に型推論に頼って型宣言まで省略してしまおう。

flip31 = (flip.)
flip32 = flip
flip33 = (flip31.flip32)
flip34 = (flip.flip31)
flip35 = (flip.flip33)


なんぞこれーw もはや、パッと見じゃなんのこっちゃわからん。
関数型プログラム言語初心者の率直な感想としては、
単に難読化しているだけのようにしか思えないわけですが(´・ω・`)
そこまでしてポイントフリースタイルで書く意味ってあんのか?


まぁ、無闇にポイントフリースタイルを追求することは、
必ずしも人を幸せにするものではないような気がする。
たぶん自己顕示欲と自己満足の世界の話なんじゃないかとw
そんなわけで、ポイントフリースタイルに対する固執は無意味、ご利用は計画的に。

でもまあ、fを無くす前まではスラスラ読み書きできるくらいではありたいかな。