{-# OPTIONS #-}

-- ------------------------------------------------------------

{- |
   Module     : Control.Sequential.MapFoldBinary
   Copyright  : Copyright (C) 2010 Uwe Schmidt
   License    : MIT

   Maintainer : Uwe Schmidt (uwe@fh-wedel.de)
   Stability  : experimental

   A map-fold function for interleaved map and fold.
   The elements of a list are processed like in a binary tree.

-}

-- ------------------------------------------------------------

module Control.Sequential.MapFoldBinary
    ( mapFoldBinary
    , mapFoldBinaryM
    )
where

-- ------------------------------------------------------------

-- | Pure version of binary map fold
--
-- @mapFoldBinary id (+) [1..8]@ adds the elements of a list in the following order:
-- @(((1+2)+(3+4))+((5+6)+(7+8)))@

mapFoldBinary                   :: (a -> b) -> (b -> b -> b) -> [a] -> b
mapFoldBinary m f xs0           = fst $ mapLeft (1::Int) $ map1 xs0
    where
    -- map1                     :: [a] -> (b, [a])
    map1 (x:xs)                 = (m x, xs)
    map1 []                     = error "mapFoldBinary with empty list"

    -- mapLeft                  :: Int -> (b, [a]) -> (b, [a])
    mapLeft n (r1, xs1)
        | null xs1              = (r1, [])
        | null xs2              = (r,  [])
        | otherwise             = mapLeft (n + 1) (r, xs2)
        where
        (r2, xs2)               = mapRight n xs1
        r                       = f r1 r2

    -- mapRight                 :: Int -> [a] -> (b, [a])
    mapRight 1 xs               = map1 xs
    mapRight n xs
        | null xs1              = (r1,      [] )
        | otherwise             = (f r1 r2, xs2)
        where
        (r1, xs1)               = mapRight (n - 1) xs
        (r2, xs2)               = mapRight (n - 1) xs1

-- ------------------------------------------------------------
{-
t1      :: [Int] -> String
t1      = mapFoldBinary show (\ x y -> "(" ++ x ++ "+" ++ y ++ ")")

r1      :: String
r1      = t1 [1..8]
-}
-- ------------------------------------------------------------

-- | Monadic version of a binary map fold
--
-- The elements of a list are mapped and folded in the same way as in the pure version.
-- The map and fold operations are interleaved. In the above example the expressions are evaluated
-- from left to right, folding is performed, as early as possible.


mapFoldBinaryM                  :: (Monad m) =>
                                   (a -> m b) -> (b -> b -> m b) -> [a] -> m b
mapFoldBinaryM m f xs0          = do
                                  r0 <- map1 xs0
                                  rn <- mapLeft (1::Int) r0
                                  return (fst rn)
    where
    -- map1                     :: [a] -> m (b, [a])
    map1 (x:xs)                 = do
                                  r1 <- m x
                                  return (r1, xs)
    map1 []                     = error "mapFoldBinary with empty list"

    -- mapLeft                  :: Int -> (b, [a]) -> m (b, [a])
    mapLeft n r@(r1, xs1)
        | null xs1              = return r
        | otherwise             = do
                                  (r2, xs2) <- mapRight n xs1
                                  res       <- f r1 r2
                                  ( if null xs2
                                    then return
                                    else mapLeft (n + 1) ) (res, xs2)

    -- mapRight                 :: Int -> [a] -> m (b, [a])
    mapRight 1 xs               = map1 xs
    mapRight n xs               = do
                                  r@(r1, xs1) <- mapRight (n - 1) xs
                                  if null xs1
                                     then return r
                                     else do
                                          (r2, xs2) <- mapRight (n - 1) xs1
                                          res       <- f r1 r2
                                          return (res, xs2)

-- ------------------------------------------------------------
{-
t1m     :: [Int] -> IO String
t1m     = mapFoldBinaryM
          ( \ x -> ( do
                     print x
                     return (show x)
                   )
          )
          ( \ x y -> ( do
                       putStrLn $ "(" ++ x ++ "+" ++ y ++ ")"
                       return   $ "(" ++ x ++ "+" ++ y ++ ")"
                     )
          )

r1m     :: IO String
r1m     = t1m [1..8]
-}
-- ------------------------------------------------------------