FibBuzz を Control.Arrow で解いてみた
何か FizzBuzz 問題をちょっとだけパワーアップさせたものに FibBuzz とかいうのがあるそうで、0 から 100 までの整数を FizzBuzz する代わりにフィボナッチ数の各項に対して FizzBuzz する問題なようです。
面白そうなので久々に Control.Arrow を引っ張り出してやってみました。
ラムダ式使わずポイントフリースタイルで書いてます。この辺は基本ですね。
import Control.Arrow import Data.Function (on) fib = loop (((fst >>> uncurry (on (.) (:))) &&& (snd &&& fst >>> app) >>> app) &&& (snd >>> (snd &&& uncurry (+) >>>) >>> (uncurry (+) &&&) >>> (>>> uncurry (:)))) (0,1) fizzBuzz = ((`mod` 3) &&& (`mod` 5) >>> f) &&& id >>> app where f (0,0) = const $ Left "FizzBuzz" f (_,0) = const $ Left "Buzz" f (0,_) = const $ Left "Fizz" f (_,_) = Right fibBuzz = arr (flip take fib >>> map fizzBuzz) >>> Kleisli (mapM_ (runKleisli (Kleisli putStrLn +++ Kleisli print))) main = runKleisli fibBuzz 30
FizzBuzz 1 1 2 Fizz Buzz 8 13 Fizz 34 Buzz 89 Fizz 233 377 Buzz Fizz 1597 2584 4181 FizzBuzz 10946 17711 28657 Fizz Buzz 121393 196418 Fizz 514229
loop 関数を初めて使ったので全く意味が分からなかったのと、mapM_ の中で Kleisli な関数を呼び出す方法で結構悩みました。
これぐらいのコードならさくっと書けるようにしたいところ。
fib 関数の Arrow 化について
fib 関数の部分だけ、ちょっとずつ Arrow 化していく様子を書いていこうと思います。
元の関数はこんな感じ。
fib = loop f (0,1) where f (p@(x,y),g) = (x:y:g p, f' g) f' g (x,y) = let z = x+y in z:g (y,z)
もうこの時点で大分あれな感じですが、とにかく最初はこんなコードでした。
で、まずは f 関数をいくつかのパーツに分解します。
-- f (p@(x,y),g) = (x:y:g p, f' g) -- f ((x,y),g) = (x:y:g (x,y), f' g) f p = ((f1 (fst p)) (f2 p), f' (snd p)) f1 (x,y) xs = x:y:xs f2 ((x,y),g) = g (x,y)
これは f 関数を適当に分解しただけです。
で、これをそれぞれ Arrow にしていきます。
-- f x = ((f1 (fst x)) (f2 x), f' (snd x)) f = ((fst >>> f1) &&& f2 >>> app) &&& (snd >>> f') -- f1 (x,y) xs = x:y:xs -- f1 (x,y) = (x:).(y:) f1 = uncurry (on (.) (:)) -- f2 ((x,y),g) = g (x,y) f2 = snd &&& fst >>> app
となります。
なので f 関数を Arrow にした状態は
fib = loop f (0,1) where f = ((fst >>> uncurry (on (.) (:))) &&& (snd &&& fst >>> app) >>> app) &&& (snd >>> f') f' g (x,y) = let z = x+y in z:g (y,z)
になります。
次に f' 関数です。例によって幾つかのパーツに分けます。
-- f' g (x,y) = let z = x+y in z:g (y,z) -- f' g (x,y) = (x+y):g (y,(x+y)) f' g p = f1' p:f2' g p f1' (x,y) = x+y f2' g (x,y) = g (y,(x+y))
で、Arrow 化していきます。 g もいきなりポイントフリースタイルにすると間違えそうなので、まずは g 以外をポイントフリースタイルにしつつ Arrow 化します。
-- f' g p = f1' p:f2' g p f' g = f1' &&& f2' g >>> uncurry (:) -- f1' (x,y) = x+y f1' = uncurry (+) -- f2' g (x,y) = g (y,(x+y)) f2' g = snd &&& uncurry (+) >>> g
で、g も消します。
-- f' g p = f1' p:f2' g p -- f' g = f1' &&& f2' g >>> uncurry (:) f' = f2' >>> (f1' &&&) >>> (>>> uncurry (:)) -- f1' (x,y) = x+y f1' = uncurry (+) -- f2' g (x,y) = g (y,(x+y)) -- f2' g = snd &&& uncurry (+) >>> g f2' = (snd &&& uncurry (+) >>>)
ということで、f' 関数を Arrow 化した状態は
fib = loop f (0,1) where f = ((fst >>> uncurry (on (.) (:))) &&& (snd &&& fst >>> app) >>> app) &&& (snd >>> f') f' = (snd &&& uncurry (+) >>>) >>> (uncurry (+) &&&) >>> (>>> uncurry (:))
となり、全部繋げると
fib = loop (((fst >>> uncurry (on (.) (:))) &&& (snd &&& fst >>> app) >>> app) &&& (snd >>> (snd &&& uncurry (+) >>>) >>> (uncurry (+) &&&) >>> (>>> uncurry (:)))) (0,1)
になります。やったねたえちゃん!