Control.Arrow (3)

http://d.hatena.ne.jp/mzsms/20091120/1258666488 のつづき。

fizzbuzz関数

(|||)の使いどころが分からなかったので、次のような関数を書いてみた:

Prelude> :l FizzBuzz.hs
[1 of 1] Compiling FizzBuzz         ( FizzBuzz.hs, interpreted )
Ok, modules loaded: FizzBuzz.
*FizzBuzz> fizzbuzz [(3, "Fizz")] [1..20]
["1","2","Fizz","4","5","Fizz","7","8","Fizz","10","11","Fizz","13","14","Fizz","16","17","Fizz","19","20"]
*FizzBuzz> fizzbuzz [(5, "Buzz")] [1..20]
["1","2","3","4","Buzz","6","7","8","9","Buzz","11","12","13","14","Buzz","16","17","18","19","Buzz"]
*FizzBuzz> fizzbuzz [(3, "Fizz"), (5, "Buzz")] [1..20]
["1","2","Fizz","4","Buzz","Fizz","7","8","Fizz","Buzz","11","Fizz","13","14","FizzBuzz","16","17","Fizz","19","Buzz"]
*FizzBuzz> fizzbuzz [(3, "Fizz"), (5, "Buzz"), (7,"Pee")] [1..105]
["1","2","Fizz","4","Buzz","Fizz","Pee","8","Fizz","Buzz","11","Fizz","13","Pee","FizzBuzz","16","17","Fizz","19","Buzz","FizzPee","22","23","Fizz","Buzz","26","Fizz","Pee","29","FizzBuzz","31","32","Fizz","34","BuzzPee","Fizz","37","38","Fizz","Buzz","41","FizzPee","43","44","FizzBuzz","46","47","Fizz","Pee","Buzz","Fizz","52","53","Fizz","Buzz","Pee","Fizz","58","59","FizzBuzz","61","62","FizzPee","64","Buzz","Fizz","67","68","Fizz","BuzzPee","71","Fizz","73","74","FizzBuzz","76","Pee","Fizz","79","Buzz","Fizz","82","83","FizzPee","Buzz","86","Fizz","88","89","FizzBuzz","Pee","92","Fizz","94","Buzz","Fizz","97","Pee","Fizz","Buzz","101","Fizz","103","104","FizzBuzzPee"]
  • (n::Int, s::String)のパラメータのリストと、数列をとって、数列の数がnで割り切れたならばsを表示する。
    • 割り切れるパラメータが複数あった場合には、文字列を連結する。
    • どのパラメータでも割り切れなければ、その数字を文字列にして表示する。

コードはつぎのとおり:

module FizzBuzz where
import Control.Arrow

if' b t e = if b then t else e
cy = curry
uy = uncurry

type I = Int
type S = [Char]
type E = Either I (I, S)

fizzbuzz :: [(I, S)] -> [I] -> [S]
fizzbuzz = step >>> map
-- fizzbuzz params = map step params

  where step    :: [(I, S)] -> I -> S
        step =                                -- params -> n -> s
                map step'                     -- map step' params   :: [E -> E]
                >>> foldl (>>>) Left
                          -- foldl (>>>) initial (map step' params) :: I -> E
                >>> (>>> show ||| snd)
                          -- foldl... >>> show ||| snd              :: I -> S

        step'  :: (I, S) -> E -> E
        step' =                  -- (inter, msg) -> \Either n (n,s) -> ...
          cy                     -- ((inter, msg), Either n (n,s))  -> ...
            ((((snd >>> id ||| fst)             -- n
               &&& (fst >>> fst)                -- inter
               >>> uy mod >>> (== 0))           -- ((n `mod` inter) == 0)
              &&& ((snd &&& fst >>> second snd) -- (E n (n, s), msg) 
                   >>> uy (stepL ||| stepR))    -- stepL n msg, stepR(n,s)msg
              >>> uy if')                       -- if ... stepL/R...
             &&& snd                            -- E n...
             >>> app)                           -- if ... stepL/R... E n...

        stepL :: I -> S -> E
        stepL = cy                               -- (n, msg)
                  Right                          -- Right (n, msg)

        stepR :: (I, S) -> S -> E
        stepR = cy                               -- ((n, s), msg)
                  ((fst >>> fst)                 -- n
                   &&& (first snd >>> uy (++))   -- s ++ msg
                   >>> Right)                    -- Right (n, s ++ msg)

Arrowは使いどころが難しいけど、うまくつかえば、関数を短くできる可能性を秘めている、ということになる気がする。例えば:

step params = foldl (>>>) Left (map step' params) >>> show ||| snd

これを、純粋に引数なしに書こうとしたら大変だけど(前記のコードに含まれている)、でもArrowを使わなければ、それはそれでもっと長くなる。結局、Arrowの使いどころ、使う程度っていうのは、こういう感じなんだと思う。

階乗関数、再び

階乗関数を書き直した:

module Fac where
import Control.Arrow

if' b t e = if b then t else e
cy = curry
uy = uncurry

fac :: Int -> Int
fac = loop ((snd &&& fst >>> app) &&& (snd >>> step))
  -- loop \(b, f) -> (f b, step f)
  -- b          :: Int
  -- f b        :: Int
  -- f, step f  :: Int -> Int
  -- step       :: (Int -> Int) -> Int -> Int

  where step :: (Int -> Int) -> Int -> Int
        step =             -- \f -> \n -> ... :: (Int -> Int) -> Int -> ...
          cy               -- \(f, n) ->  ... :: ((Int -> Int), Int) -> ...
            ((snd >>> (/= 1))                   -- n /= 1       :: Bool 
             &&& ((second (flip (-) 1) >>> app) -- f (n - 1)    :: Int
                  &&& snd                       -- n            :: Int
                  >>> uy (*))     -- f (n - 1) * n              :: Int
             >>> uy if'           -- if' (n/=1) (f n-1) * n)
             >>> ($ 1))           -- if' (n/=1) (f n-1) * n) 1  :: Int

もう一回、書き直し:

module Fac where
import Control.Arrow

if' b t e = if b then t else e

fac :: Int -> Int
fac =
  loop                           -- (b, f)
    (   (snd &&& fst) &&& snd    -- ((f, b), f)
     >>> app          *** step)  -- (f b, step f)

  -- loop (\(b, f) -> (f b, step f))
  -- b          :: Int
  -- f b        :: Int
  -- f, step f  :: Int -> Int
  -- step       :: (Int -> Int) -> Int -> Int

  where step :: (Int -> Int) -> Int -> Int
        step =
          curry                                         -- (f,n)
            (    snd    &&& snd &&& fst &&& snd         -- (n,(n,(f,n))
             >>> id     *** id  *** id  *** flip (-) 1  -- (n,(n,(f,n-1)))
             >>> id     *** id  *** app                 -- (n,(n, f n-1))
             >>> (/= 1) *** uncurry (*)                 -- (n/=1,n*(f n-1))
             >>> uncurry if'                            -- if' n/=1 n*(f n-1)
             >>> ($ 1))                                 -- if' ... ... 1

fizzbuzz関数、再び

fizzbuzz関数がもっと短く書けそうなので、試行錯誤中…

module FizzBuzz where
import Control.Arrow

if' b t e = if b then t else e

fizzbuzz :: [(Int, String)] -> [Int] -> [String]
fizzbuzz = step >>> map

step :: [(Int, String)] -> Int -> String
step =                       -- params -> n
  curry                      -- (params, n)
    ((snd >>> Right)         -- Right n
     &&& (second step' >>> snd &&& fst >>> uncurry map) -- map (step' n) params
     >>> uncurry (foldr con) -- folder con (Right n) (map (step' n) params)
     >>> id ||| show)

step' :: Int -> (Int, String) -> Either String Int
step' =                                      -- n (inter, msg) 
  curry                                      -- (n, (inter, msg))
    ((second fst >>> uncurry mod >>> (== 0)) -- n `mod` inter == 0
     &&& (snd >>> snd >>> Left)              -- Left msg
     >>> uncurry if'                         -- if' (n`mod`inter==0)(Left msg)
     >>> ($ Right 0))                        -- if' ... ... (Right 0)

con :: Either String Int -> Either String Int -> Either String Int
con =                                           -- E s n -> E s' n'
  (flip                                         --   E s' n' -> s
    ((curry                                     --     (s', s)
        (snd &&& fst >>> uncurry(++) >>> Left)) --     Left s' ++ s
     ||| const Left))                           -- n' -> s' ->... => Left s'
  ||| const id                                  -- E s' n'

-- fizzbuzz :: [(Int, String)] -> [Int] -> [String]
-- fizzbuzz params list =  map (step params) list

-- step :: [(Int, String)] -> Int -> String
-- step params n = (id ||| show) $ foldr con (Right n) (map (step' n) params)

-- step' :: Int -> (Int, String) -> Either String Int
-- step' n (inter, msg) = if n `mod` inter == 0 then Left msg else Right 0

-- con :: Either String Int -> Either String Int -> Either String Int
-- con (Left s) (Left s')   = Left (s ++ s')
-- con (Left s) (Right _)   = Left s
-- con (Right _) (Left s')  = Left s'
-- con (Right _) (Right n)  = Right n

(つづく)