-- |
-- Module      : Foundation.Primitive.Mappable
-- License     : BSD-style
-- Maintainer  : Nicolas Di Prima <nicolas@primetype.co.uk>
-- Stability   : experimental
-- Portability : portable
--
-- Class of collection that can be traversed from left to right,
-- performing an action on each element.
--
module Foundation.Collection.Mappable
    ( Mappable(..)
    , sequence_
    , traverse_
    , mapM_
    , forM
    , forM_
    ) where

import           Foundation.Internal.Base
import qualified Data.Traversable
import           Foundation.Array.Boxed (Array)

-- | Functors representing data structures that can be traversed from
-- left to right.
--
-- Mostly like base's `Traversable` but applied to collections only.
--
class Functor collection => Mappable collection where
    {-# MINIMAL traverse | sequenceA #-}

    -- | Map each element of a structure to an action, evaluate these actions
    -- from left to right, and collect the results. For a version that ignores
    -- the results see 'Foundation.Collection.traverse_'.
    traverse :: Applicative f => (a -> f b)
                              -> collection a
                              -> f (collection b)
    traverse f = sequenceA . fmap f

    -- | Evaluate each actions of the given collections, from left to right,
    -- and collect the results. For a version that ignores the results, see
    -- `Foundation.Collection.sequenceA_`
    sequenceA :: Applicative f => collection (f a)
                               -> f (collection a)
    sequenceA = traverse id

    -- | Map each element of the collection to an action, evaluate these actions
    -- from left to right, and collect the results. For a version that ignores
    -- the results see 'Foundation.Collection.mapM_'.
    mapM :: (Applicative m, Monad m) => (a -> m b) -> collection a -> m (collection b)
    mapM = traverse

    -- | Evaluate each actions of the given collections, from left to right,
    -- and collect the results. For a version that ignores the results, see
    -- `Foundation.Collection.sequence_`
    sequence :: (Applicative m, Monad m) => collection (m a) -> m (collection a)
    sequence = sequenceA

-- | Map each element of a collection to an action, evaluate these
-- actions from left to right, and ignore the results. For a version
-- that doesn't ignore the results see 'Foundation.Collection.traverse`
traverse_ :: (Mappable col, Applicative f) => (a -> f b) -> col a -> f ()
traverse_ f col = traverse f col *> pure ()

-- | Evaluate each action in the collection from left to right, and
-- ignore the results. For a version that doesn't ignore the results
-- see 'Foundation.Collection.sequenceA'.
sequenceA_ :: (Mappable col, Applicative f) => col (f a) -> f ()
sequenceA_ col = sequenceA col *> pure ()

-- | Map each element of a collection to a monadic action, evaluate
-- these actions from left to right, and ignore the results. For a
-- version that doesn't ignore the results see
-- 'Foundation.Collection.mapM'.
mapM_ :: (Mappable col, Applicative m, Monad m) => (a -> m b) -> col a -> m ()
mapM_ f c = mapM f c *> return ()

-- | Evaluate each monadic action in the collection from left to right,
-- and ignore the results. For a version that doesn't ignore the
-- results see 'Foundation.Collection.sequence'.
sequence_ :: (Mappable col, Applicative m, Monad m) => col (m a) -> m ()
sequence_ c = sequence c *> return ()

-- | 'forM' is 'mapM' with its arguments flipped. For a version that
-- ignores the results see 'Foundation.Collection.forM_'.
forM :: (Mappable col, Applicative m, Monad m) => col a -> (a -> m b) -> m (col b)
forM = flip mapM

-- | 'forM_' is 'mapM_' with its arguments flipped. For a version that
-- doesn't ignore the results see 'Foundation.Collection.forM'.
forM_ :: (Mappable col, Applicative m, Monad m) => col a -> (a -> m b) -> m ()
forM_ = flip mapM_

----------------------------
-- Foldable instances
----------------------------

instance Mappable [] where
    {-# INLINE traverse #-}
    traverse = Data.Traversable.traverse

instance Mappable Array where
    -- | TODO: to optimise
    traverse f arr = fromList <$> traverse f (toList arr)