{-# LANGUAGE TypeOperators #-}

module ZkFold.Base.Data.Package where

import           Data.Foldable             (Foldable)
import           Data.Function             ((.))
import           Data.Functor              (Functor (..))
import           GHC.Generics              (Par1 (..), (:.:) (..))

import           ZkFold.Base.Data.HFunctor (HFunctor (..))

-- | A @'Package'@ is a higher-order functor (@'HFunctor'@) which allows to
-- (un)pack layered structures of nested functors.
class HFunctor c => Package c where
  {-# MINIMAL (unpack | unpackWith), (pack | packWith) #-}

  -- | Unpacks the outer layer of a package.
  -- Note that it is almost always better to define 'unpackWith' instead
  -- and rely on the default implementation of 'unpack'.
  -- The following should hold:
  --
  -- [Definition] @'unpack' p == 'unpackWith' 'unComp1' p@
  unpack :: Functor f => c (f :.: g) -> f (c g)
  unpack = (forall (a :: k1). (:.:) f g a -> f (g a))
-> c (f :.: g) -> f (c g)
forall {k1} (c :: (k1 -> Type) -> Type) (f :: Type -> Type)
       (h :: k1 -> Type) (g :: k1 -> Type).
(Package c, Functor f) =>
(forall (a :: k1). h a -> f (g a)) -> c h -> f (c g)
forall (f :: Type -> Type) (h :: k1 -> Type) (g :: k1 -> Type).
Functor f =>
(forall (a :: k1). h a -> f (g a)) -> c h -> f (c g)
unpackWith (:.:) f g a -> f (g a)
forall (a :: k1). (:.:) f g a -> f (g a)
forall k2 k1 (f :: k2 -> Type) (g :: k1 -> k2) (p :: k1).
(:.:) f g p -> f (g p)
unComp1

  -- | Given a way to peel the outer layer, unpacks it.
  -- If @'unpack'@ is specified instead, a default definition is available.
  -- The following should hold:
  --
  -- [Definition] @'unpackWith' f p == 'unpack' ('hmap' ('Comp1' '.' f) p)@
  -- [Compatibility] @'hmap' f p == 'unPar1' ('unpackWith' ('Par1' '.' f) p)@
  unpackWith :: Functor f => (forall a. h a -> f (g a)) -> c h -> f (c g)
  unpackWith forall (a :: k1). h a -> f (g a)
f = c (f :.: g) -> f (c g)
forall {k1} (c :: (k1 -> Type) -> Type) (f :: Type -> Type)
       (g :: k1 -> Type).
(Package c, Functor f) =>
c (f :.: g) -> f (c g)
forall (f :: Type -> Type) (g :: k1 -> Type).
Functor f =>
c (f :.: g) -> f (c g)
unpack (c (f :.: g) -> f (c g)) -> (c h -> c (f :.: g)) -> c h -> f (c g)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall (a :: k1). h a -> (:.:) f g a) -> c h -> c (f :.: g)
forall {k} (c :: (k -> Type) -> Type) (f :: k -> Type)
       (g :: k -> Type).
HFunctor c =>
(forall (a :: k). f a -> g a) -> c f -> c g
forall (f :: k1 -> Type) (g :: k1 -> Type).
(forall (a :: k1). f a -> g a) -> c f -> c g
hmap (f (g a) -> (:.:) f g a
forall k2 k1 (f :: k2 -> Type) (g :: k1 -> k2) (p :: k1).
f (g p) -> (:.:) f g p
Comp1 (f (g a) -> (:.:) f g a) -> (h a -> f (g a)) -> h a -> (:.:) f g a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. h a -> f (g a)
forall (a :: k1). h a -> f (g a)
f)

  -- | Packs the outer layer into the package.
  -- Note that it is almost always better to define 'packWith' instead
  -- and rely on the default implementation of 'pack'.
  -- The following should hold:
  --
  -- [Definition] @'pack' p == 'packWith' 'Comp1' p@
  -- [Inverse] @'pack' ('unpack' p) == p@
  pack :: (Foldable f, Functor f) => f (c g) -> c (f :.: g)
  pack = (forall (a :: k1). f (g a) -> (:.:) f g a)
-> f (c g) -> c (f :.: g)
forall {k1} (c :: (k1 -> Type) -> Type) (f :: Type -> Type)
       (g :: k1 -> Type) (h :: k1 -> Type).
(Package c, Foldable f, Functor f) =>
(forall (a :: k1). f (g a) -> h a) -> f (c g) -> c h
forall (f :: Type -> Type) (g :: k1 -> Type) (h :: k1 -> Type).
(Foldable f, Functor f) =>
(forall (a :: k1). f (g a) -> h a) -> f (c g) -> c h
packWith f (g a) -> (:.:) f g a
forall (a :: k1). f (g a) -> (:.:) f g a
forall k2 k1 (f :: k2 -> Type) (g :: k1 -> k2) (p :: k1).
f (g p) -> (:.:) f g p
Comp1

  -- | Given a way to merge the outer layer, packs it.
  -- If 'pack' is specified instead, a default definition if available.
  -- The following should hold:
  --
  -- [Definition] @'packWith' f p == 'hmap' (f '.' 'unComp1') ('pack' p)@
  -- [Compatibility] @'hmap' f p == 'packWith' (f . 'unPar1') ('Par1' p)@
  packWith :: (Foldable f, Functor f) => (forall a. f (g a) -> h a) -> f (c g) -> c h
  packWith forall (a :: k1). f (g a) -> h a
f = (forall (a :: k1). (:.:) f g a -> h a) -> c (f :.: g) -> c h
forall {k} (c :: (k -> Type) -> Type) (f :: k -> Type)
       (g :: k -> Type).
HFunctor c =>
(forall (a :: k). f a -> g a) -> c f -> c g
forall (f :: k1 -> Type) (g :: k1 -> Type).
(forall (a :: k1). f a -> g a) -> c f -> c g
hmap (f (g a) -> h a
forall (a :: k1). f (g a) -> h a
f (f (g a) -> h a) -> ((:.:) f g a -> f (g a)) -> (:.:) f g a -> h a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (:.:) f g a -> f (g a)
forall k2 k1 (f :: k2 -> Type) (g :: k1 -> k2) (p :: k1).
(:.:) f g p -> f (g p)
unComp1) (c (f :.: g) -> c h) -> (f (c g) -> c (f :.: g)) -> f (c g) -> c h
forall b c a. (b -> c) -> (a -> b) -> a -> c
. f (c g) -> c (f :.: g)
forall {k1} (c :: (k1 -> Type) -> Type) (f :: Type -> Type)
       (g :: k1 -> Type).
(Package c, Foldable f, Functor f) =>
f (c g) -> c (f :.: g)
forall (f :: Type -> Type) (g :: k1 -> Type).
(Foldable f, Functor f) =>
f (c g) -> c (f :.: g)
pack

-- | Performs the full unpacking.
unpacked :: (Package c, Functor f) => c f -> f (c Par1)
unpacked :: forall (c :: (Type -> Type) -> Type) (f :: Type -> Type).
(Package c, Functor f) =>
c f -> f (c Par1)
unpacked = (forall a. f a -> f (Par1 a)) -> c f -> f (c Par1)
forall {k1} (c :: (k1 -> Type) -> Type) (f :: Type -> Type)
       (h :: k1 -> Type) (g :: k1 -> Type).
(Package c, Functor f) =>
(forall (a :: k1). h a -> f (g a)) -> c h -> f (c g)
forall (f :: Type -> Type) (h :: Type -> Type) (g :: Type -> Type).
Functor f =>
(forall a. h a -> f (g a)) -> c h -> f (c g)
unpackWith ((a -> Par1 a) -> f a -> f (Par1 a)
forall a b. (a -> b) -> f a -> f b
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> Par1 a
forall p. p -> Par1 p
Par1)

-- | Performs the full package.
packed :: (Package c, Foldable f, Functor f) => f (c Par1) -> c f
packed :: forall (c :: (Type -> Type) -> Type) (f :: Type -> Type).
(Package c, Foldable f, Functor f) =>
f (c Par1) -> c f
packed = (forall a. f (Par1 a) -> f a) -> f (c Par1) -> c f
forall {k1} (c :: (k1 -> Type) -> Type) (f :: Type -> Type)
       (g :: k1 -> Type) (h :: k1 -> Type).
(Package c, Foldable f, Functor f) =>
(forall (a :: k1). f (g a) -> h a) -> f (c g) -> c h
forall (f :: Type -> Type) (g :: Type -> Type) (h :: Type -> Type).
(Foldable f, Functor f) =>
(forall a. f (g a) -> h a) -> f (c g) -> c h
packWith ((Par1 a -> a) -> f (Par1 a) -> f a
forall a b. (a -> b) -> f a -> f b
forall (f :: Type -> Type) a b. Functor f => (a -> b) -> f a -> f b
fmap Par1 a -> a
forall p. Par1 p -> p
unPar1)