{-|

Module      : Data.Semialign.Merge
Copyright   : (c) 2019, Commonwealth Scientific and Industrial Research Organisation
License     : BSD3
Maintainer  : jack.kelly@data61.csiro.au
Stability   : experimental
Portability : Portable

The 'Semialign' typeclass lets us line up two structures of the same
type. We can use this to merge two structures, or add additional
typeclasses to do filtering, 'Applicative' effects, or tracking
indices.

'merge' is the simplest function. It takes five arguments: functions
to handle values present in the left \/ right \/ both structures and
two structures to merge.

@
merge :: Semialign t
  => (a -> c) -> (b -> c) -> (a -> b -> c)
  -> t a -> t b
  -> t c
@

Every other function in this module is a variant with modified
functionality based on its name:

* Prefix @i@ means an "indexed" variant: each function argument takes
  an additional index (@i@) parameter.

* Suffix @Maybe@ means "filter results": each function argument
  returns @Maybe c@, and @Nothing@s are filtered out.

* Suffix @A@ means "applicative": each function argument returns
  actions in some 'Applicative' @f@, and the whole operation collects
  these actions to produce the a merged structure in @f@.

-}

module Data.Semialign.Merge
  ( merge
  , mergeMaybe
  , mergeA
  , mergeMaybeA

  -- * Indexed Variants
  , imerge
  , imergeMaybe
  , imergeA
  , imergeMaybeA
  ) where

import Data.Semialign (Semialign(..))
import Data.Semialign.Indexed (SemialignWithIndex(..))
import Data.These (these)
import Data.Witherable (Filterable(..), Witherable(..))

-- | @since 0.1.0.0
merge
  :: Semialign t
  => (a -> c)
  -> (b -> c)
  -> (a -> b -> c)
  -> t a
  -> t b
  -> t c
merge f g h = alignWith (these f g h)

-- | @since 0.1.0.0
mergeMaybe
  :: (Filterable t, Semialign t)
  => (a -> Maybe c)
  -> (b -> Maybe c)
  -> (a -> b -> Maybe c)
  -> t a
  -> t b
  -> t c
mergeMaybe f g h = (catMaybes .) . alignWith (these f g h)

-- | @since 0.1.0.0
mergeA
  :: (Applicative f, Semialign t, Traversable t)
  => (a -> f c)
  -> (b -> f c)
  -> (a -> b -> f c)
  -> t a
  -> t b
  -> f (t c)
mergeA f g h = (sequenceA .) . alignWith (these f g h)

-- | @since 0.1.0.0
mergeMaybeA
  :: (Applicative f, Semialign t, Witherable t)
  => (a -> f (Maybe c))
  -> (b -> f (Maybe c))
  -> (a -> b -> f (Maybe c))
  -> t a
  -> t b
  -> f (t c)
mergeMaybeA f g h = (wither id .) . alignWith (these f g h)

-- | @since 0.1.0.0
imerge
  :: (SemialignWithIndex i t)
  => (i -> a -> c)
  -> (i -> b -> c)
  -> (i -> a -> b -> c)
  -> t a
  -> t b
  -> t c
imerge f g h = ialignWith (these <$> f <*> g <*> h)

-- | @since 0.1.0.0
imergeMaybe
  :: (Filterable t, SemialignWithIndex i t)
  => (i -> a -> Maybe c)
  -> (i -> b -> Maybe c)
  -> (i -> a -> b -> Maybe c)
  -> t a
  -> t b
  -> t c
imergeMaybe f g h = (catMaybes .) . ialignWith (these <$> f <*> g <*> h)

-- | @since 0.1.0.0
imergeA
  :: (Applicative f, SemialignWithIndex i t, Traversable t)
  => (i -> a -> f c)
  -> (i -> b -> f c)
  -> (i -> a -> b -> f c)
  -> t a
  -> t b
  -> f (t c)
imergeA f g h = (sequenceA .) . ialignWith (these <$> f <*> g <*> h)

-- | @since 0.1.0.0
imergeMaybeA
  :: (Applicative f, SemialignWithIndex i t, Witherable t)
  => (i -> a -> f (Maybe c))
  -> (i -> b -> f (Maybe c))
  -> (i -> a -> b -> f (Maybe c))
  -> t a
  -> t b
  -> f (t c)
imergeMaybeA f g h = (wither id .) . ialignWith (these <$> f <*> g <*> h)