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

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

import Language.Symantic
import Language.Symantic.Lib.Function (a0, b1)

-- * Class 'Sym_Functor'
type instance Sym Functor = Sym_Functor
class Sym_Functor term where
        fmap :: Functor f => term (a -> b) -> term (f a) -> term (f b)
        default fmap :: Sym_Functor (UnT term) => Trans term => Functor f => term (a -> b) -> term (f a) -> term (f b)
        fmap = trans2 fmap

        (<$>) :: (Sym_Functor term, Functor f) => term (a -> b) -> term (f a) -> term (f b); infixl 4 <$>
        (<$>) = fmap

        (<$) :: Functor f => term a -> term (f b) -> term (f a); infixl 4 <$
        default (<$) :: Sym_Lambda term => Functor f => term a -> term (f b) -> term (f a)
        (<$) x = fmap (lam (Fun.const x))

-- Interpreting
instance Sym_Functor Eval where
        fmap  = eval2 Functor.fmap
        (<$)  = eval2 (Functor.<$)
instance Sym_Functor View where
        fmap  = view2 "fmap"
        (<$>) = viewInfix "<$>" (infixL 4)
        (<$)  = viewInfix "<$"  (infixL 4)
instance (Sym_Functor r1, Sym_Functor r2) => Sym_Functor (Dup r1 r2) where
        fmap = dup2 @Sym_Functor fmap
        (<$) = dup2 @Sym_Functor (<$)

-- Transforming
instance (Sym_Functor term, Sym_Lambda term) => Sym_Functor (BetaT term) where
        (<$>) = trans2 (<$>)
        (<$)  = trans2 (<$)

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

-- Compiling
instance Gram_Term_AtomsFor src ss g Functor
instance (Source src, SymInj ss Functor) => ModuleFor src ss Functor where
        moduleFor = ["Functor"] `moduleWhere`
         [ "fmap" := teFunctor_fmap
         , "<$"  `withInfixL` 4 := teFunctor_const
         , "<$>" `withInfixL` 4 := teFunctor_fmap_infix
         ]

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

f1 :: Source src => LenInj vs => KindInj (K f) =>
      Type src (a ': Proxy f ': vs) f
f1 = tyVar "f" $ VarS varZ

f2 :: Source src => LenInj vs => KindInj (K f) =>
      Type src (a ': b ': Proxy f ': vs) f
f2 = tyVar "f" $ VarS $ VarS varZ

-- ** 'Term's
teFunctor_fmap, teFunctor_fmap_infix :: TermDef Functor '[Proxy a, Proxy b, Proxy f] (Functor f #> ((a -> b) -> f a -> f b))
teFunctor_fmap = Term (tyFunctor f2) ((a0 ~> b1) ~> f2 `tyApp` a0 ~> f2 `tyApp` b1) $ teSym @Functor $ lam2 fmap
teFunctor_fmap_infix = Term (tyFunctor f2) ((a0 ~> b1) ~> f2 `tyApp` a0 ~> f2 `tyApp` b1) $ teSym @Functor $ lam2 (<$>)

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