{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
-- | Symantic for 'Applicative'.
module Language.Symantic.Lib.Applicative where

import Control.Applicative (Applicative)
import Prelude hiding (Functor(..), (<$>), Applicative(..), id, const)
import qualified Control.Applicative as Applicative
import qualified Data.Function as Fun

import Language.Symantic
import Language.Symantic.Lib.Function (a0, b1)
import Language.Symantic.Lib.Functor (Sym_Functor(..), (<$>), f1, f2)

-- * Class 'Sym_Applicative'
type instance Sym Applicative = Sym_Applicative
class Sym_Functor term => Sym_Applicative term where
	pure  :: Applicative f => term a -> term (f a)
	(<*>) :: Applicative f => term (f (a -> b)) -> term (f a) -> term (f b); infixl 4 <*>
	(*>)  :: Applicative f => term (f a) -> term (f b) -> term (f b); infixl 4 *>
	(<*)  :: Applicative f => term (f a) -> term (f b) -> term (f a); infixl 4 <*
	
	default pure  :: Sym_Applicative (UnT term) => Trans term => Applicative f => term a -> term (f a)
	default (<*>) :: Sym_Applicative (UnT term) => Trans term => Applicative f => term (f (a -> b)) -> term (f a) -> term (f b)
	default (*>)  :: Sym_Lambda term => Applicative f => term (f a) -> term (f b) -> term (f b)
	default (<*)  :: Sym_Lambda term => Applicative f => term (f a) -> term (f b) -> term (f a)
	
	pure   = trans1 pure
	(<*>)  = trans2 (<*>)
	x *> y = lam1 Fun.id    <$  x <*> y
	x <* y = lam2 Fun.const <$> x <*> y

-- Interpreting
instance Sym_Applicative Eval where
	pure  = eval1 Applicative.pure
	(<*>) = eval2 (Applicative.<*>)
instance Sym_Applicative View where
	pure  = view1 "pure"
	(<*>) = viewInfix "<*>" (infixL 4)
	(<* ) = viewInfix "<*"  (infixL 4)
	( *>) = viewInfix "*>"  (infixL 4)
instance (Sym_Applicative r1, Sym_Applicative r2, Sym_Lambda r1, Sym_Lambda r2) => Sym_Applicative (Dup r1 r2) where
	pure  = dup1 @Sym_Applicative pure
	(<*>) = dup2 @Sym_Applicative (<*>)

-- Transforming
instance (Sym_Lambda term, Sym_Applicative term) => Sym_Applicative (BetaT term) where
	(<*) = trans2 (<*)
	(*>) = trans2 (*>)

-- Typing
instance NameTyOf Applicative where
	nameTyOf _c = ["Applicative"] `Mod` "Applicative"
instance FixityOf Applicative
instance ClassInstancesFor Applicative
instance TypeInstancesFor Applicative

-- Compiling
instance Gram_Term_AtomsFor src ss g Applicative
instance (Source src, SymInj ss Applicative) => ModuleFor src ss Applicative where
	moduleFor = ["Applicative"] `moduleWhere`
	 [ "<*>" `withInfixL` 4 := teApplicative_app
	 , "<*"  `withInfixL` 4 := teApplicative_const
	 , "*>"  `withInfixL` 4 := teApplicative_tsnoc
	 ]

-- ** 'Type's
tyApplicative :: Source src => Type src vs a -> Type src vs (Applicative a)
tyApplicative a = tyConstLen @(K Applicative) @Applicative (lenVars a) `tyApp` a

-- ** 'Term's
teApplicative_pure :: TermDef Applicative '[Proxy a, Proxy f] (Applicative f #> (a -> f a))
teApplicative_pure = Term (tyApplicative f1) (a0 ~> f1 `tyApp` a0) $ teSym @Applicative $ lam1 pure

teApplicative_app :: TermDef Applicative '[Proxy a, Proxy b, Proxy f] (Applicative f #> (f (a -> b) -> f a -> f b))
teApplicative_app = Term (tyApplicative f2) (f2 `tyApp` (a0 ~> b1) ~> f2 `tyApp` a0 ~> f2 `tyApp` b1) $ teSym @Applicative $ lam2 (<*>)

teApplicative_const :: TermDef Applicative '[Proxy a, Proxy b1, Proxy f] (Applicative f #> (f a -> f b1 -> f a))
teApplicative_const = Term (tyApplicative f2) (f2 `tyApp` a0 ~> f2 `tyApp` b1 ~> f2 `tyApp` a0) $ teSym @Applicative $ lam2 (<*)

teApplicative_tsnoc :: TermDef Applicative '[Proxy a, Proxy b, Proxy f] (Applicative f #> (f a -> f b -> f b))
teApplicative_tsnoc = Term (tyApplicative f2) (f2 `tyApp` a0 ~> f2 `tyApp` b1 ~> f2 `tyApp` b1) $ teSym @Applicative $ lam2 (*>)