{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LinearTypes #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_HADDOCK hide #-}

module Data.Functor.Linear.Internal.Applicative
  ( Applicative (..),
    genericPure,
    genericLiftA2,
  )
where

import qualified Control.Monad.Trans.Reader as NonLinear
import Data.Functor.Compose
import Data.Functor.Const
import Data.Functor.Identity
import Data.Functor.Linear.Internal.Functor
import Data.Functor.Product
import Data.Monoid (Ap (..))
import Data.Monoid.Linear hiding (Product)
import Data.Unrestricted.Linear.Internal.Ur (Ur (..))
import GHC.TypeLits
import GHC.Types
import Prelude.Linear.Generically
import Prelude.Linear.Internal
import Prelude.Linear.Unsatisfiable
import qualified Prelude

-- # Applicative definition
-------------------------------------------------------------------------------

-- | Data 'Applicative'-s can be seen as containers which can be zipped
-- together. A prime example of data 'Applicative' are vectors of known length
-- ('ZipList's would be, if it were not for the fact that zipping them together
-- drops values, which we are not allowed to do in a linear container).
--
-- In fact, an applicative functor is precisely a functor equipped with (pure
-- and) @liftA2 :: (a %1-> b %1-> c) -> f a %1-> f b %1-> f c@. In the case where
-- @f = []@, the signature of 'liftA2' would specialise to that of 'zipWith'.
--
-- Intuitively, the type of 'liftA2' means that 'Applicative's can be seen as
-- containers whose "number" of elements is known at compile-time. This
-- includes vectors of known length but excludes 'Maybe', since this may
-- contain either zero or one value.  Similarly, @((->) r)@ forms a Data
-- 'Applicative', since this is a (possibly infinitary) container indexed by
-- @r@, while lists do not, since they may contain any number of elements.
--
-- == Remarks for the mathematically inclined
--
-- An 'Applicative' is, as in the restricted case, a lax monoidal endofunctor of
-- the category of linear types. That is, it is equipped with
--
-- * a (linear) function @() %1-> f ()@
-- * a (linear) natural transformation @(f a, f b) %1-> f (a, b)@
--
-- It is a simple exercise to verify that these are equivalent to the definition
-- of 'Applicative'. Hence that the choice of linearity of the various arrow is
-- indeed natural.
class (Functor f) => Applicative f where
  {-# MINIMAL pure, (liftA2 | (<*>)) #-}
  pure :: a -> f a
  (<*>) :: f (a %1 -> b) %1 -> f a %1 -> f b
  infixl 4 <*> -- same fixity as base.<*>
  f <*> x = liftA2 ($) f x
  liftA2 :: (a %1 -> b %1 -> c) -> f a %1 -> f b %1 -> f c
  liftA2 f x y = f <$> x <*> y

-- # Instances
-------------------------------------------------------------------------------

deriving via
  Generically1 (Const x)
  instance
    (Monoid x) => Applicative (Const x)

deriving via
  Generically1 Ur
  instance
    Applicative Ur

deriving via
  Generically1 ((,) a)
  instance
    (Monoid a) => Applicative ((,) a)

deriving via
  Generically1 (Product (f :: Type -> Type) g)
  instance
    (Applicative f, Applicative g) => Applicative (Product f g)

deriving via
  Generically1 ((f :: Type -> Type) :*: g)
  instance
    (Applicative f, Applicative g) => Applicative (f :*: g)

deriving via
  Generically1 ((,,) a b)
  instance
    (Monoid a, Monoid b) => Applicative ((,,) a b)

deriving via
  Generically1 ((,,,) a b c)
  instance
    (Monoid a, Monoid b, Monoid c) => Applicative ((,,,) a b c)

deriving via
  Generically1 Identity
  instance
    Applicative Identity

instance (Applicative f, Applicative g) => Applicative (Compose f g) where
  pure x = Compose (pure (pure x))
  (Compose f) <*> (Compose x) = Compose (liftA2 (<*>) f x)
  liftA2 f (Compose x) (Compose y) = Compose (liftA2 (liftA2 f) x y)

instance (Applicative m) => Applicative (NonLinear.ReaderT r m) where
  pure x = NonLinear.ReaderT (\_ -> pure x)
  NonLinear.ReaderT f <*> NonLinear.ReaderT x = NonLinear.ReaderT (\r -> f r <*> x r)

instance (Applicative f, Semigroup a) => Semigroup (Ap f a) where
  (Ap x) <> (Ap y) = Ap $ liftA2 (<>) x y

instance (Applicative f, Monoid a) => Monoid (Ap f a) where
  mempty = Ap $ pure mempty

-- ----------------
-- Generic deriving
-- ----------------

instance
  (Generic1 f, Functor (Rep1 f), GApplicative ('ShowType f) (Rep1 f)) =>
  Applicative (Generically1 f)
  where
  pure = Generically1 Prelude.. genericPure
  liftA2 f (Generically1 x) (Generically1 y) = Generically1 (genericLiftA2 f x y)

genericPure ::
  forall f a.
  (Generic1 f, GApplicative ('ShowType f) (Rep1 f)) =>
  a ->
  f a
genericPure = to1 Prelude.. gpure @('ShowType f)

genericLiftA2 ::
  forall f a b c.
  (Generic1 f, GApplicative ('ShowType f) (Rep1 f)) =>
  (a %1 -> b %1 -> c) ->
  f a %1 ->
  f b %1 ->
  f c
genericLiftA2 f x y = to1 (gliftA2 @('ShowType f) f (from1 x) (from1 y))

class GApplicative (s :: ErrorMessage) f where
  gpure :: a -> f a
  gliftA2 :: (a %1 -> b %1 -> c) -> f a %1 -> f b %1 -> f c

instance
  ( Unsatisfiable
      ( 'Text "Cannot derive a data Applicative instance for"
          ':$$: s
          ':$$: 'Text "because empty types cannot implement pure."
      )
  ) =>
  GApplicative s V1
  where
  gpure = unsatisfiable
  gliftA2 = unsatisfiable

instance GApplicative s U1 where
  gpure _ = U1
  gliftA2 _ U1 U1 = U1
  {-# INLINE gpure #-}
  {-# INLINE gliftA2 #-}

instance (GApplicative s f) => GApplicative s (M1 i c f) where
  gpure = M1 Prelude.. gpure @s
  gliftA2 f (M1 x) (M1 y) = M1 (gliftA2 @s f x y)
  {-# INLINE gpure #-}
  {-# INLINE gliftA2 #-}

instance GApplicative s Par1 where
  gpure = Par1
  gliftA2 f (Par1 x) (Par1 y) = Par1 (f x y)
  {-# INLINE gpure #-}
  {-# INLINE gliftA2 #-}

instance (GApplicative s f, Applicative g) => GApplicative s (f :.: g) where
  gpure = Comp1 Prelude.. gpure @s Prelude.. pure
  gliftA2 f (Comp1 x) (Comp1 y) = Comp1 (gliftA2 @s (liftA2 f) x y)
  {-# INLINE gpure #-}
  {-# INLINE gliftA2 #-}

instance (GApplicative s f, GApplicative s g) => GApplicative s (f :*: g) where
  gpure a = gpure @s a :*: gpure @s a
  gliftA2 f (a1 :*: a2) (b1 :*: b2) = gliftA2 @s f a1 b1 :*: gliftA2 @s f a2 b2
  {-# INLINE gpure #-}
  {-# INLINE gliftA2 #-}

instance
  ( Unsatisfiable
      ( 'Text "Cannot derive a data Applicative instance for"
          ':$$: s
          ':$$: 'Text "because sum types do not admit a uniform Applicative definition."
      )
  ) =>
  GApplicative s (x :+: y)
  where
  gpure = unsatisfiable
  gliftA2 = unsatisfiable

instance (GApplicative s f) => GApplicative s (MP1 m f) where
  gpure a = MP1 (gpure @s a)
  gliftA2 f (MP1 a) (MP1 b) = MP1 (gliftA2 @s f a b)
  {-# INLINE gpure #-}
  {-# INLINE gliftA2 #-}

instance (Monoid c) => GApplicative s (K1 i c) where
  gpure _ = K1 mempty
  gliftA2 _ (K1 x) (K1 y) = K1 (x <> y)
  {-# INLINE gpure #-}
  {-# INLINE gliftA2 #-}

instance
  ( Unsatisfiable
      ( 'Text "Cannot derive a data Applicative instance for"
          ':$$: s
          ':$$: 'Text "because it contains one or more primitive unboxed fields."
          ':$$: 'Text "Such unboxed types lack canonical monoid operations."
      )
  ) =>
  GApplicative s (URec a)
  where
  gpure = unsatisfiable
  gliftA2 = unsatisfiable
