    {-# 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
      |])