{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Singletons.Prelude.Monad.Zip
-- Copyright   :  (C) 2018 Ryan Scott
-- License     :  BSD-style (see LICENSE)
-- Maintainer  :  Ryan Scott
-- Stability   :  experimental
-- Portability :  non-portable
--
-- Defines the promoted and singled versions of the 'MonadZip' type class.
--
----------------------------------------------------------------------------

module Data.Singletons.Prelude.Monad.Zip (
  PMonadZip(..), SMonadZip(..),

  -- * Defunctionalization symbols
  MzipSym0, MzipSym1, MzipSym2,
  MzipWithSym0, MzipWithSym1, MzipWithSym2, MzipWithSym3,
  MunzipSym0, MunzipSym1,
  ) where

import Control.Monad.Zip
import Data.Functor.Identity
import Data.Kind
import Data.Monoid
import Data.Singletons.Prelude.Identity
import Data.Singletons.Prelude.Instances
import Data.Singletons.Prelude.List
       ( ZipSym0, ZipWithSym0, UnzipSym0
       , sZip,    sZipWith,    sUnzip )
import Data.Singletons.Prelude.Monad.Internal
import Data.Singletons.Prelude.Monoid ()
import Data.Singletons.Prelude.Tuple
import Data.Singletons.Single

$(singletonsOnly [d|
  -- -| `MonadZip` type class. Minimal definition: `mzip` or `mzipWith`
  --
  -- Instances should satisfy the laws:
  --
  -- -* Naturality :
  --
  --   > liftM (f *** g) (mzip ma mb) = mzip (liftM f ma) (liftM g mb)
  --
  -- -* Information Preservation:
  --
  --   > liftM (const ()) ma = liftM (const ()) mb
  --   > ==>
  --   > munzip (mzip ma mb) = (ma, mb)
  --
  class Monad m => MonadZip (m :: Type -> Type) where
      -- {-# MINIMAL mzip | mzipWith #-}

      mzip :: m a -> m b -> m (a,b)
      mzip = mzipWith (,)

      mzipWith :: (a -> b -> c) -> m a -> m b -> m c
      mzipWith f ma mb = liftM (uncurry f) (mzip ma mb)

      munzip :: m (a,b) -> (m a, m b)
      munzip mab = (liftM fst mab, liftM snd mab)
      -- munzip is a member of the class because sometimes
      -- you can implement it more efficiently than the
      -- above default code.  See Trac #4370 comment by giorgidze

  instance MonadZip [] where
      mzip     = zip
      mzipWith = zipWith
      munzip   = unzip

  instance MonadZip Identity where
      mzipWith = liftM2
      munzip (Identity (a, b)) = (Identity a, Identity b)

  instance MonadZip Dual where
      -- Cannot use coerce, it's unsafe
      mzipWith = liftM2

  instance MonadZip Sum where
      mzipWith = liftM2

  instance MonadZip Product where
      mzipWith = liftM2

  instance MonadZip Maybe where
      mzipWith = liftM2

  instance MonadZip First where
      mzipWith = liftM2

  instance MonadZip Last where
      mzipWith = liftM2
  |])