{-|
Module: Data.Quiver.Functor
Description: free categories
Copyright: (c) Eitan Chatav, 2019
Maintainer: eitan@morphism.tech
Stability: experimental

Consider the category of Haskell quivers with

* objects are types of higher kind
  * @p :: k -> k -> Type@
* morphisms are terms of @RankNType@,
  * @forall x y. p x y -> q x y@
* identity is `id`
* composition is `.`

There is a natural hierarchy of typeclasses for
endofunctors of the category of Haskell quivers,
analagous to that for Haskell types.
-}

{-# LANGUAGE
    PolyKinds
  , RankNTypes
#-}

module Data.Quiver.Functor
  ( QFunctor (..)
  , QPointed (..)
  , QFoldable (..)
  , QTraversable (..)
  , QMonad (..)
  ) where

import Control.Category
import Data.Quiver
import Prelude hiding (id, (.))

{- | An endfunctor of quivers.

prop> qmap id = id
prop> qmap (g . f) = qmap g . qmap f
-}
class QFunctor c where
  qmap :: (forall x y. p x y -> q x y) -> c p x y -> c q x y
instance QFunctor (ProductQ p) where qmap f (ProductQ p q) = ProductQ p (f q)
instance QFunctor (HomQ p) where qmap g (HomQ f) = HomQ (g . f)
instance Functor t => QFunctor (ApQ t) where qmap f (ApQ t) = ApQ (f <$> t)
instance QFunctor OpQ where qmap f = OpQ . f . getOpQ
instance QFunctor IsoQ where qmap f (IsoQ u d) = IsoQ (f u) (f d)
instance QFunctor IQ where qmap f = IQ . f . getIQ
instance QFunctor (ComposeQ p) where qmap f (ComposeQ p q) = ComposeQ p (f q)
instance QFunctor (LeftQ p) where qmap g (LeftQ f) = LeftQ (g . f)
instance QFunctor (RightQ p) where qmap g (RightQ f) = RightQ (g . f)

{- | Embed a single quiver arrow with `qsingle`.-}
class QFunctor c => QPointed c where qsingle :: p x y -> c p x y
instance QPointed (HomQ p) where qsingle q = HomQ (const q)
instance Applicative t => QPointed (ApQ t) where qsingle = ApQ . pure
instance QPointed IQ where qsingle = IQ
instance Category p => QPointed (ComposeQ p) where qsingle = ComposeQ id

{- | Generalizing `Foldable` from `Monoid`s to `Category`s.

prop> qmap f = qfoldMap (qsingle . f)
-}
class QFunctor c => QFoldable c where
  {- | Map each element of the structure to a `Category`,
  and combine the results.-}
  qfoldMap :: Category q => (forall x y. p x y -> q x y) -> c p x y -> q x y
  {- | Combine the elements of a structure using a `Category`.-}
  qfold :: Category q => c q x y -> q x y
  qfold = qfoldMap id
  {- | Right-associative fold of a structure.

  In the case of `Control.Category.Free.Path`s,
  `qfoldr`, when applied to a binary operator,
  a starting value, and a `Control.Category.Free.Path`,
  reduces the `Control.Category.Free.Path` using the binary operator,
  from right to left:

  prop> qfoldr (?) q (p1 :>> p2 :>> ... :>> pn :>> Done) == p1 ? (p2 ? ... (pn ? q) ...)
  -}
  qfoldr :: (forall x y z . p x y -> q y z -> q x z) -> q y z -> c p x y -> q x z
  qfoldr (?) q c = getRightQ (qfoldMap (\ x -> RightQ (\ y -> x ? y)) c) q
  {- | Left-associative fold of a structure.

  In the case of `Control.Category.Free.Path`s,
  `qfoldl`, when applied to a binary operator,
  a starting value, and a `Control.Category.Free.Path`,
  reduces the `Control.Category.Free.Path` using the binary operator,
  from left to right:

  prop> qfoldl (?) q (p1 :>> p2 :>> ... :>> pn :>> Done) == (... ((q ? p1) ? p2) ? ...) ? pn
  -}
  qfoldl :: (forall x y z . q x y -> p y z -> q x z) -> q x y -> c p y z -> q x z
  qfoldl (?) q c = getLeftQ (qfoldMap (\ x -> LeftQ (\ y -> y ? x)) c) q
  {- | Map each element of the structure to a `Monoid`,
  and combine the results.-}
  qtoMonoid :: Monoid m => (forall x y. p x y -> m) -> c p x y -> m
  qtoMonoid f = getKQ . qfoldMap (KQ . f)
  {- | Map each element of the structure, and combine the results in a list.-}
  qtoList :: (forall x y. p x y -> a) -> c p x y -> [a]
  qtoList f = qtoMonoid (pure . f)
  {- | Map each element of a structure to an `Applicative` on a `Category`,
  evaluate from left to right, and combine the results.-}
  qtraverse_
    :: (Applicative m, Category q)
    => (forall x y. p x y -> m (q x y)) -> c p x y -> m (q x y)
  qtraverse_ f = getApQ . qfoldMap (ApQ . f)
instance QFoldable (ProductQ p) where qfoldMap f (ProductQ _ q) = f q
instance QFoldable IQ where qfoldMap f (IQ c) = f c

{- | Generalizing `Traversable` to quivers.-}
class QFoldable c => QTraversable c where
  {- | Map each element of a structure to an `Applicative` on a quiver,
  evaluate from left to right, and collect the results.-}
  qtraverse
    :: Applicative m
    => (forall x y. p x y -> m (q x y)) -> c p x y -> m (c q x y)
instance QTraversable (ProductQ p) where
  qtraverse f (ProductQ p q) = ProductQ p <$> f q
instance QTraversable IQ where qtraverse f (IQ c) = IQ <$> f c

{- | Generalize `Monad` to quivers.

Associativity and left and right identity laws hold.

prop> qjoin . qjoin = qjoin . qmap qjoin
prop> qjoin . qsingle = id
prop> qjoin . qmap qsingle = id

The functions `qbind` and `qjoin` are related as

prop> qjoin = qbind id
prop> qbind f p = qjoin (qmap f p)
-}
class (QFunctor c, QPointed c) => QMonad c where
  qjoin :: c (c p) x y -> c p x y
  qjoin = qbind id
  qbind :: (forall x y. p x y -> c q x y) -> c p x y -> c q x y
  qbind f p = qjoin (qmap f p)
  {-# MINIMAL qjoin | qbind #-}
instance QMonad (HomQ p) where
  qjoin (HomQ q) = HomQ (\p -> getHomQ (q p) p)
instance Monad t => QMonad (ApQ t) where
  qbind f (ApQ t) = ApQ $ do
    p <- t
    getApQ $ f p
instance QMonad IQ where qjoin = getIQ
instance Category p => QMonad (ComposeQ p) where
  qjoin (ComposeQ yz (ComposeQ xy q)) = ComposeQ (yz . xy) q