{-|
Module      : Nom
Description : Nominal-flavoured implementation of data in a context of local names
Copyright   : (c) Murdoch J. Gabbay, 2020
License     : GPL-3
Maintainer  : murdoch.gabbay@gmail.com
Stability   : experimental
Portability : POSIX

Nominal-flavoured implementation of data in a context of local names
-}

{-# LANGUAGE ConstraintKinds
           , DataKinds
           , DeriveFunctor
           , DeriveFoldable
           , DeriveTraversable
           , DeriveGeneric
           , DerivingVia
           , FlexibleContexts
           , FlexibleInstances
           , FunctionalDependencies
           , GADTs 
           , GeneralizedNewtypeDeriving
           , InstanceSigs
           , MultiParamTypeClasses
           , PartialTypeSignatures
           , PatternSynonyms
           , ScopedTypeVariables
           , StandaloneDeriving
           , TypeApplications
           , TypeFamilies
           , TypeOperators
           , ViewPatterns
           , UndecidableInstances
#-}

{-# OPTIONS_GHC -fno-warn-orphans #-} -- Suppress orphan instance of Data instance of Binder ctxbody.

module Language.Nominal.Nom
   ( -- * The Nom monad
     -- $nom
       KNom (..), Nom
     -- * Creating a @'Nom'@
     , res, kres, resN, reRes
     -- * Destroying a @'Nom'@
     , unNom, nomToIO
     -- * Creating fresh ids in a @'Nom'@
     , freshKAtom, freshAtom, freshKAtoms, freshAtoms
     , freshKName, freshName, freshKNames, freshNames
     , freshKNameIO, freshNameIO, freshKNamesIO, freshNamesIO
     -- * 'KNom' and other functors
     -- $functor
     , transposeNomF
     -- * Tests
     -- $tests
     , module Language.Nominal.SMonad
   )
   where

-- import Data.Data        hiding (typeRep, TypeRep)
-- import Data.Function    ((&))
-- import Data.Proxy
import Data.Type.Equality
import Type.Reflection
import Control.Applicative  -- for Alternative
import Control.Compose -- for :.
import System.IO.Unsafe (unsafePerformIO)
import Data.Functor
import GHC.Generics     hiding (Prefix) -- avoid clash with Data.Data.Prefix
-- import Flow             ((.>))

import Language.Nominal.Name
import Language.Nominal.NameSet
import Language.Nominal.SMonad
import Language.Nominal.Utilities


-- * The Nom monad

{- $nom

Broadly speaking there are three kinds of functions involving 'KNom':

* 'KNom'-specific functions that may involve creating fresh atoms, but do not involve swappings.
* More general functions involving the 'Language.Nominal.Binder.Binder' typeclass (defined using 'KNom', and of which 'KNom' is the canonical instance).  These functions tend to impose 'Swappable' and 'Typeable' constraints on their type parameters.
* 'KNom'-specific functions in which, for various reasons (elegance, avoiding code duplication, necessity) we make use of both classes of functions above.

This file deals with the first class above.  The second and third are in "Language.Nominal.Binder".

/Remark: 'KNom' is more basic than 'Language.Nominal.Binder.Binder', though it's the canonical instance.  This creates a design tension: is a function on 'KNom' best viewed as a function directly on Nom, or as an instance of a more general function involving Binder?/

-}



-- | Data in local @'KAtom' s@-binding context.
newtype KNom s a = Nom { getNom :: IO (XRes s a) -- ^ Recover a name-generating IO action from a 'KNom' binding.  If you execute one of these using 'unsafePerformIO', you get the data in @a@ (the /body/ of the binding), with some actual freshly-generated atoms.  See also 'unNom'.
                       }
   deriving (Generic)
   deriving (Functor, Applicative, Monad) via ViaSMonad (KNom s)
--   deriving (KSupport s) via BinderSupp (KNom s a) -- avoid forward dependency

-- | Swap goes under name-binders.
--
-- > swp n1 n2 (ns @> x) = (swp n1 n2 ns) @> (swp n1 n2 x)
instance (Typeable s, Swappable a) => Swappable (KNom s a) where
   kswp n1 n2 (Nom x) = Nom $ kswp n1 n2 <$> x

instance SMonad [KAtom s] (KNom s) where
    (>>+)  :: KNom s a -> ([KAtom s] -> a -> KNom s b) -> KNom s b
    (Nom a') >>+ cont = Nom $ do -- IO monad
        a <- a'
        let (atms0, a0) = exitWith (,) a
        let (Nom b') = cont atms0 a0
        iact atms0 <$> b'
    -- | Enters a name-binding context. 
    --
    -- /Note: This is a low-level instruction.  It does not enforce capture-avoidance.  Only use it if you know what you're doing; if in doubt use @'res'@ instead, which is higher-level but cost typeclass assumptions)./
    enter :: [KAtom s] -> a -> KNom s a
    enter = Nom . return .: enter
    -- | Exits a name-binding context, using 'unsafePerformIO'. 
    --
    -- /Note: This is a low-level instruction.  Only use it if you know what you're doing; if in doubt use @'unNom'@ or destructors from "Language.Nominal.Binder" instead, which are higher-level but cost typeclass assumptions./
    --
    -- /Note: This is our only use of 'unsafePerformIO'.  If a fresh ID got generated, then it came from here./
    exit :: KNom s a -> a
    exit = exit . unsafePerformIO . getNom

-- | Data in a local atom-binding context at @'Tom'@s.
type Nom = KNom Tom


instance (KSupport t a, Typeable s) => KSupport t (KNom s a) where
   ksupp p x' = unNom $ ksupp p <$> x' -- use of unNom garbage-collects restricted atoms

-- | 'KRestrict' @atms@ in @a@, inside 'KNom' monad.
instance {-# OVERLAPPING #-} (Typeable s, Swappable a) => KRestrict s (KNom s a) where
   restrict = (=<<) . res
instance {-# OVERLAPPABLE #-} (Typeable s, Swappable a, KRestrict t a) => KRestrict t (KNom s a) where
   restrict atms x' = case testEquality (typeRep :: (TypeRep t)) (typeRep :: (TypeRep s)) of
-- are s and t the same type?  (this should never trigger, because it should be captured by overlapping instance)
            Just Refl   -> x' >>= res atms
-- are s and t different types?
            Nothing     -> restrict atms <$> x'

-- | Alternatives are composed in a capture-avoiding manner
instance Alternative f => Alternative ((KNom s) :. f) where
   empty = O $ return empty
   a' <|> b' = O $ do -- Nom monad
      a <- unO a'
      b <- unO b'
      return $ a <|> b



-- * Creating a 'KNom'

-- | Constructor for @'KNom' s@. 
res :: (Typeable s, Swappable a) => [KAtom s] -> a -> KNom s a
res atms a = freshKAtoms atms <&> \atms' -> perm (zip atms' atms) a

-- | Constructor for @'KNom' s@ (proxy version). 
kres :: (Typeable s, Swappable a) => proxy s -> [KAtom s] -> a -> KNom s a
kres _ = res

-- | Make sure restricted atoms will alpha-convert to avoid capture, if e.g. they were created using 'enter' and not 'res'. 
reRes :: (Typeable s, Swappable a) => KNom s a -> KNom s a
reRes = exitWith res

-- | A version of 'res' on 'Nom' that takes names, not atoms (it just strips the labels of the names and acts on their atoms).
resN :: (Typeable s, Swappable a) => [KName s t] -> a -> KNom s a
resN = res . fmap nameAtom



-- | Use this to safely exit a @'KNom'@ monad.
-- It works by binding the @KNom@-bound @s@-atoms using @a@'s native instance of @KRestrict@.  See "Language.Nominal.Examples.Tutorial" for examples.
--
-- > unNom = resAppC id
--
-- /Note: The less versions of 'unNom' are the 'exit' and 'exitWith' instances of 'KNom' as an instance of 'SMonad'.  See also 'Language.Nominal.Binder.@@'./ 
unNom :: KRestrict s a => KNom s a -> a
unNom = exitWith restrict

-- | Another way to safely exit 'KNom': convert it to an IO action (the IO may generate fresh names) 
nomToIO :: KNom s a -> IO a
nomToIO (Nom a') = exit <$> a'


-- * Creating fresh ids in a @'Nom'@

-- | Create a fresh atom-in-nominal-context
freshKAtom :: KNom s (KAtom s)
freshKAtom = Nom $ do -- IO monad
   [a] <- freshKAtomsIO [()]
   return $ enter [a] a

-- | Canonical version for unit atoms sort.
freshAtom :: Nom Atom
freshAtom = freshKAtom

-- | Fresh @'Traversable' m@ of atoms (e.g. @m@ is list or stream)
freshKAtoms :: Traversable m => m t -> KNom s (m (KAtom s))
freshKAtoms = mapM (const freshKAtom)

-- | Fresh @'Traversable' m@ of atoms (e.g. @m@ is list or stream).
-- | 'Tom' instance.
freshAtoms :: Traversable m => m t -> Nom (m Atom)
freshAtoms = freshKAtoms


-- | Create a fresh name-in-a-nominal-context with label @t@
freshKName :: t -> KNom s (KName s t)
freshKName t = freshKAtom <&> Name t

-- | Create fresh names-in-a-nominal-context
freshKNames :: Traversable m => m t -> KNom s (m (KName s t))
freshKNames = mapM freshKName

-- | Canonical version of 'freshKName' for @'Tom'@ name.
freshName :: t -> Nom (Name t)
freshName = freshKName

-- | Canonical version of 'freshKNames' for @'Tom'@ names.
freshNames :: Traversable m => m t -> Nom (m (Name t))
freshNames = freshKNames


-- | Create a fresh name-in-a-nominal-context with label @t@
freshKNameIO :: t -> IO (KName s t)
freshKNameIO = nomToIO . freshKName

-- | Create fresh names-in-a-nominal-context
freshKNamesIO :: Traversable m => m t -> IO (m (KName s t))
freshKNamesIO = nomToIO . freshKNames

-- | Canonical version of 'freshKName' for @'Tom'@ name.
freshNameIO :: t -> IO (Name t)
freshNameIO = nomToIO . freshKName

-- | Canonical version of 'freshKNames' for @'Tom'@ names.
freshNamesIO :: Traversable m => m t -> IO (m (Name t))
freshNamesIO = nomToIO . freshKNames


{-- 
-- | atFresh f returns the value of f at a fresh name with label @t@
atFresh :: Typeable s => t -> (KName s t -> a) -> KNom s a
atFresh t f = freshKName t <&> f
--}

-- * 'KNom' and functors

{- $functor

There are three functions that will commute 'KNom' with some other 'f':

* 'transposeNomF'
* 'transposeMF'
* 'transposeFM'

Taken together, these functions are making a point that 'KNom' is compatible with your favourite container type.  Because 'KNom' commutes, there is no need to wonder whether (for example) a graph-with-binding should be a graph with binding on the vertices, or on the edges, or on the graph overall, or any combination.  All of these are valid design decisions and one may be more /convenient/ than the other, but we know we can isomorphically commute to a single top-level 'KNom' binding.

In that sense, 'KNom' captures a general theory of binding.
This also mathematically justifies why the 'Language.Nominal.Binder.Binder' typeclass is so useful.
-}


-- | 'KNom' commutes down through functors.
--
-- 'transposeNomF' is a version of 'transposeMF' where bindings are have added capture-avoidance (for inverse, see 'transposeFM'). 
transposeNomF :: (Functor f, Typeable s, Swappable a) => KNom s (f a) -> f (KNom s a)
transposeNomF = exitWith $ fmap . res
-- transposeNomFunc = exitWith $ fmap . enter 


-- * `Nom` and `Eq`

-- | Fresh names are generated when name contexts are opened for equality test
instance Eq a => Eq (KNom s a) where
   a1' == a2' = exit $ a1' >>= \a1 -> a2' >>= \a2 -> return $ a1 == a2
-- Rendering using 'Binder' machinery is possible, but adds forward dependencies and imposes typeclass condition:
-- instance (Typeable s, Swappable a, Eq a) => Eq (KNom s a) where
--   a1' == a2' = a1' @@. \_ a1 -> a2' @@. \_ a2 -> a1 == a2

-- | Show a nom by unpacking it
instance Show a => Show (KNom s a) where
    show = exitWith $ \atms a -> show atms ++ "." ++ show a


{- $tests Property-based tests are in "Language.Nominal.Properties.NomSpec". -}