{-|
Module      : Binder 
Description : Typeclass theory of binders 
Copyright   : (c) Murdoch J. Gabbay, 2020
License     : GPL-3
Maintainer  : murdoch.gabbay@gmail.com
Stability   : experimental
Portability : POSIX

Typeclass theory of binders 

-}

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

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

module Language.Nominal.Binder
   ( -- * The @'Binder'@ typeclass
     -- $binder
       Binder (..), (@@!), (@@.), pattern (:@@!), nomApp, nomAppC, genApp, genAppC, resApp, resAppC, resAppC'
     -- * @'new'@ quantifier
     , newA, new, freshForA, freshFor
     -- * Detecting trivial @'KNom'@s, that bind no atoms in their argument
     , isTrivialNomBySupp, isTrivialNomByEq
     -- * @'Nom'@ and @'supp'@
     , kfreshen, freshen, BinderSupp (..)
     -- * Binder isomorphism
     -- $binderiso
     , kbinderToNom, knomToBinder, knomToMaybeBinder, binderToNom, nomToBinder, nomToMaybeBinder
     -- * Binder with concretion
     , BinderConc (..)
     -- * Tests
     -- $tests
   )
   where

import Data.Data
import Data.Maybe
import Data.Set         as S (toList) -- , Set) -- , union)
import GHC.Generics     hiding (Prefix) -- avoid clash with Data.Data.Prefix

import Language.Nominal.Utilities
import Language.Nominal.NameSet
import Language.Nominal.Name
import Language.Nominal.Nom


-- * Binder

{- $binder

'Binder' is a typeclass for creating and unpacking local bindings.
The current development contains three instances of 'Binder':

* 'KNom' itself: the primitive binding construct of this package.
* 'Language.Nominal.Abs.KAbs': abstraction by a single name.
* 'Language.Nominal.Examples.IdealisedEUTxO.Chunk': a (generalisation of) EUTxO-style blockchains (the binding is in essence the names of edges connecting input-output pairs).

The simplest way to make a binder is to wrap a 'KNom' in further structure:

* 'Language.Nominal.Examples.IdealisedEUTxO.Chunk' has this design.
Its internal representation is structurally just a 'KNom' (what makes it interesting is that it is wrapped in some smart contructors that validate nontrivial extra structure, involving binding and more).

However, it is possible to design a binder without explicitly mentioning 'KNom' in its underlying representation:

* 'Language.Nominal.Abs.KAbs' is an instance.  For technical reasons its underlying representation uses functions, although a bijection does exist with a 'KNom'-based structure (see 'Language.Nominal.Abs.absToNom' and 'Language.Nominal.Abs.nomToAbs').

What makes 'Binder' interesting is a rich array of destructors (e.g. '@@', '@@!', '@@.', 'nomApp', 'nomAppC', 'genApp', 'genAppC', 'resApp', 'resAppC', 'resAppC'').
These correspond to common patterns of unpacking a binder and mapping to other types, and provide a consistent interface across different binding constructs.

-}

-- | A typeclass for unpacking local binding.
--
-- Provides a function to unpack a binder, giving access to its locally bound names.  Examples of binders include 'KNom', 'Language.Nominal.Abs.KAbs', and 'Language.Nominal.Examples.IdealisedEUTxO.Chunk'.
--
-- A 'Binder' comes with a rich array of destructors, like '@@', '@@!', '@@.', 'nomApp', 'nomAppC', 'genApp', 'genAppC', 'resApp', 'resAppC', 'resAppC''.
-- These correspond to common patterns of unpacking a binder and mapping to other types.
--
-- Below:
--
-- * @ctxbody@ is the type of the datum in its name-binding context, which consists intuitively of data wrapped in a local name binding.  (Canonical example: 'KNom'.)
-- * @ctx@ is the type of a concrete name-carrying representation of the local binding (atom, names, list of atoms, etc).  (Canonical examples: 'KAtom' and @['KAtom']@.)
-- * @body@ is the type of the body of the data.
-- * @s@ is the type of atoms output.  Output will be wrapped in @'KNom' s@.
--
-- For example the 'Language.Nominal.Abs.KAbs' instance of 'Binder' has type @Binder (KAbs (KName s t) a) (KName s t) s a@; and this means that
--
-- * a datum in @ctxbody = KAbs (KName s t) a@ can be unpacked as
-- * something in @ctx = KName s t@ and something in @body = a@, and
-- * mapped by a function of type @KName s t -> a -> b@ using '@@',
-- * to obtain something in @KNom s b@.
--
-- The other destructors ('@@!', '@@.', 'nomApp', 'nomAppC', 'genApp', 'genAppC', 'resApp', 'resAppC', 'resAppC'') are variations on this basic pattern which the wider development shows are empirically useful.
--
-- See also 'kbinderToNom' and 'nomToMaybeBinder'.
class (Typeable s, Swappable ctx, Swappable body, Swappable ctxbody) => Binder ctxbody ctx body s | ctxbody -> ctx body s  where
   -- | A destructor for the binder (required)
   (@@) :: ctxbody           -- ^ the data in its local binding
       -> (ctx -> body -> b) -- ^ function that inputs an explicit name context @ctx@ and a @body@ for that context, and outputs a @b@.
       -> KNom s b           -- ^ Result is a @b@ in a binding context.
   (@>) :: ctx -> body -> ctxbody
   -- ^ A constructor for the binder (optional, since e.g. your instance may impose additional well-formedness constraints on the input).  Call this @res@.
   (@>) = fromJust .: resMay
   -- | A partial constructor for the binder, if preferred.
   resMay :: ctx -> body -> Maybe ctxbody
   resMay = Just .: (@>)
   {-# MINIMAL (@@) #-} -- Please at least provide the destructor (@@).  The constructor is optional, since e.g. your instance may impose additional well-formedness constraints on the input.
infixr 9 @@
infixr 1 @>
-- documentation doesn't appear above for (@>) unless I use -- ^


-- | Use this to map a binder to a type @b@, generating fresh atoms for any local bindings, and allowing them to escape.
(@@!) :: Binder ctxbody ctx body s => ctxbody -> (ctx -> body -> b) -> b
(@@!) = exit .: (@@)
infixr 9 @@!

-- | This pattern is not quite as useful as it could be, because it's too polymorphic to use a COMPLETE pragma on.  You can use it, but you may get incomplete pattern match errors.
pattern (:@@!) :: Binder ctxbody ctx body s => ctx -> body -> ctxbody
pattern ctx :@@! body <- ((@@! (,)) -> (ctx, body))
   where (:@@!) = (@>)
-- {-# COMPLETE (:@@!) #-}   -- States this match is complete.
infixr 0 :@@!

-- | Acts on a 'KNom' binder by applying a function to the body and the context of locally bound names.  Local names are preserved.
instance (Typeable s, Swappable a) => Binder (KNom s a) [KAtom s] a s where
   (@@) :: KNom s a -> ([KAtom s] -> a -> b) -> KNom s b
   (@@) = pami . reRes -- the @reRes@ ensures capture-avoidance, in case the input binding was formed using 'enter' instead of 'res'.
   -- (@@) a' f = imap f $ withExit a' res   -- flip imap 
   (@>) :: (Typeable s, Swappable a) => [KAtom s] -> a -> KNom s a
   (@>) = res

-- | Use this to map a binder to a type @b@ with its own notion of restriction.  Bindings do not escape.
(@@.) :: (Binder ctxbody ctx body s, KRestrict s b) => ctxbody -> (ctx -> body -> b) -> b
(@@.) x' f = unNom $ x' @@ f

infixr 9 @@.



-- | Unpacks a binder and maps into a @'KNom'@ environment.
--
-- 'nomApp' = 'flip' '(@@)'
nomApp :: Binder ctxbody ctx body s => (ctx -> body -> b) -> ctxbody -> KNom s b
nomApp = flip (@@)
-- | Unpacks a binder and maps into a @'KNom'@ environment.
-- Local bindings are not examined, and get carried over to the @'KNom'@.
--
-- 'nomAppC' = 'nomApp' . 'const'
nomAppC :: Binder ctxbody ctx body s => (body -> b) -> ctxbody -> KNom s b
nomAppC = nomApp . const
-- | Unpacks a binder.  New names are generated and released.
--
-- 'genApp' = 'flip' '(@@!)'
genApp :: Binder ctxbody ctx body s => (ctx -> body -> b) -> ctxbody -> b
genApp = flip (@@!)
-- | Unpacks a binder.  New names are generated and released.
-- Local bindings are not examined.
--
-- 'genAppC' = 'genApp' . 'const'
genAppC :: Binder ctxbody ctx body s => (body -> b) -> ctxbody -> b
genAppC = genApp . const
-- | Unpacks a binder and maps to a type with its own @'restrict'@ operation.
--
-- 'resApp' = 'flip' '(@@.)'
resApp :: (Binder ctxbody ctx body s, KRestrict s b) => (ctx -> body -> b) -> ctxbody -> b
resApp = flip (@@.)

-- | Unpacks a binder and maps to a type with its own @'restrict'@ operation.
-- Local bindings are not examined, and get carried over and @'restrict'@ed in the target type.
--
-- A common type instance is @(a -> Bool) -> KNom s a -> Bool@, in which case 'resAppC' acts to capture-avoidingly apply a predicate under a binder, and return the relevant truth-value.  See for example the code for 'transposeNomFunc'.
--
-- 'resAppC' = 'resApp' . 'const'
resAppC :: (Binder ctxbody ctx body s, KRestrict s b) => (body -> b) -> ctxbody -> b
resAppC = resApp . const
-- | Unpacks a binder and maps to a type with its own @'restrict'@ operation.  'Flip'ped version, for convenience.
--
-- A common type instance is @KNom s a -> (a -> Bool) -> Bool @, in which case 'resAppC'' acts to apply a predicate inside a binder and return the relevant truth-value.  See for example the code for 'transposeNomMaybe'.
--
-- 'resAppC'' = 'flip' 'resApp'
resAppC' :: (Binder ctxbody ctx body s, KRestrict s b) => ctxbody -> (body -> b) -> b
resAppC' = flip resAppC


-- * Variants on 'res'


{-- | A version of '@>' (@res@) that accepts a proxy to explicitly specify the type of atoms.
kres :: (Typeable s, Swappable body, Binder ctxbody ctx body s) => proxy s -> ctx -> body -> ctxbody
kres _ = (@>)
--}



-- * New quantifier

-- | Evaluate predicate on fresh atom.
newA :: KRestrict s b => (KAtom s -> b) -> b
newA = resAppC' freshKAtom

-- | Evaluate predicate on fresh name with label @t@.
new :: (KRestrict s b, Swappable t) => t -> (KName s t -> b) -> b
new = resAppC' . freshKName


-- | @n@ is @'freshFor'@ @a@ when @swp n' n a == a@ for a @'new'@ @n'@.  Straight out of the paper (<https://link.springer.com/article/10.1007/s001650200016 publisher> and <http://www.gabbay.org.uk/papers.html#newaas-jv author's> pdfs).
--
-- /Note: In practice we mostly need this for names.  This version is for atoms.
freshForA :: (Typeable s, Swappable a, Eq a) => KAtom s -> a -> Bool
freshForA n a = newA $ \n' -> kswp n' n a == a

-- | @n@ is @'freshFor'@ @a@ when @swp n' n a == a@ for a @'new'@ @n'@.  Straight out of the paper (<https://link.springer.com/article/10.1007/s001650200016 publisher> and <http://www.gabbay.org.uk/papers.html#newaas-jv author's> pdfs).
freshFor :: (Typeable s, Swappable t, Swappable a, Eq a) => KName s t -> a -> Bool
freshFor = freshForA . nameAtom


-- * Detecting trivial @'KNom'@s, that bind no atoms in their argument

-- | Is the @'Nom'@ binding trivial?
--
-- This is the @'KSupport'@ version, for highly structured data that we can traverse (think: lists, tuples, sums, and maps).
--
-- See also @'isTrivialNomByEq'@, for less structured data when we have equality @'Eq'@ and swapping 'KSwappable'.
isTrivialNomBySupp :: forall s a. KSupport s a => KNom s a -> Bool
isTrivialNomBySupp = resApp $ kapart (Proxy :: Proxy s)

-- | Is the @'Nom'@ binding trivial?
--
-- This is the @'Eq'@ version, using @'freshFor'@.
-- Use this when we /do/ have equality and swapping, but /can't/ (or don't want to) traverse the type (think: sets).
--
-- See also @'isTrivialNomBySupp'@, for when we have @'KSupport'@.
isTrivialNomByEq :: (Typeable s, Swappable a, Eq a) => KNom s a -> Bool
isTrivialNomByEq = resApp $ \atms a -> and $ (`freshForA` a) <$> atms


-- * @'KNom'@ and @'KSupport'@


-- | Freshen all @s@-atoms, if we have @'KSupport'@.  Returns result inside @'KNom'@ binding to store the freshly-generated atoms.
kfreshen :: KSupport s a => proxy s -> a -> KNom s a
kfreshen p a = S.toList (ksupp p a) @> a

-- | Freshen all atoms, if we have @'Support'@.
freshen :: Support a => a -> Nom a
freshen = kfreshen atTom

-- | Wrapper for generic method of deriving support of binder
newtype BinderSupp a = BinderSupp {getBinderSupp :: a}
   deriving (Generic, Swappable)

instance (Binder ctxbody ctx body s, KSupport s ctx, KSupport s body) => KSupport s (BinderSupp ctxbody) where
   ksupp p (BinderSupp ctxbody) = ctxbody @@. \ctx body -> ksupp p (ctx, body)  -- Use of '(@@.)' here cleans out bound atoms.

{-- | Standard method to calculate the support of a binder
ksuppBinder :: (Binder ctxbody ctx body s, KSupport s ctx, KSupport s body) => proxy s -> ctxbody -> S.Set (KAtom s)
ksuppBinder = resAppC . ksupp -- Use of 'resAppC' here cleans out the (atom of the) abstracted name.
-- | @supp (ns @> x) == supp x \\ ns@
instance KSupport s a => KSupport s (KNom s a) where
   ksupp = ksuppBinder
--}



-- * Binder isomorphism

{- $binderiso

The 'Binder' class has two functions pointing in opposite directions.
This suggests a standard isomorphism.  We expect:

> binderToNom <$> nomToMaybeBinder x == Just x
> nomToBinder . binderToNom == id

For 'KNom' and 'KAbs' it's safe to use 'knomToBinder'.  For others, like 'Language.Nominal.Examples.IdealisedEUTxO.Chunk', you would need 'knomToMaybeBinder', because additional well-formedness conditions are imposed on the constructor.
-}

kbinderToNom :: Binder ctxbody ctx body s => proxy ctxbody -> ctxbody -> KNom s (ctx, body)
kbinderToNom _ = nomApp (,)

knomToMaybeBinder :: (Typeable s, Swappable ctx, Swappable body, Binder ctxbody ctx body s) => proxy ctxbody -> KNom s (ctx, body) -> Maybe ctxbody
knomToMaybeBinder _ = genAppC $ uncurry resMay

knomToBinder :: (Typeable s, Swappable ctx, Swappable body, Binder ctxbody ctx body s) => proxy ctxbody -> KNom s (ctx, body) -> ctxbody
knomToBinder = fromJust .: knomToMaybeBinder

binderToNom :: Binder ctxbody ctx body s => ctxbody -> KNom s (ctx, body)
binderToNom = kbinderToNom (Proxy :: Proxy ctxbody)

nomToMaybeBinder :: (Typeable s, Swappable ctx, Swappable body, Binder ctxbody ctx body s) => KNom s (ctx, body) -> Maybe ctxbody
nomToMaybeBinder = knomToMaybeBinder (Proxy :: Proxy ctxbody)

nomToBinder :: (Typeable s, Swappable ctx, Swappable body, Binder ctxbody ctx body s) => KNom s (ctx, body) -> ctxbody
nomToBinder = knomToBinder (Proxy :: Proxy ctxbody)

-- * Scrap your boilerplate generic support

binderD :: DataType
binderD = mkDataType "Binder" [binderC]

binderC :: Constr
binderC = mkConstr binderD "(@>)" [] Prefix

instance {-# OVERLAPPABLE #-} (Binder ctxbody ctx body s, Typeable ctxbody, Data ctx, Data body) => Data ctxbody where

    gfoldl k z ctxbody = ctxbody @@! \ctx body -> z (@>) `k` ctx `k` body
    gunfold k z _ = k $ k $ z (@>)
    toConstr   = const binderC
    dataTypeOf = const binderD

-- * Binder with concretion

-- | Sometimes we can /concrete/ a bound entity @ctxbody@ at a datum @a@ (where it need not be the case that @a == ctx@), to return a body @body@.  In the case that @a == ctx@, we expect
--
-- > (a @> x) `conc` a == x
-- > a @> (x' `conc` a) == x
class Binder ctxbody ctx body s => BinderConc ctxbody ctx body s a where
   conc :: ctxbody -> a -> body
   conc = flip cnoc
   cnoc :: a -> ctxbody -> body
   cnoc = flip conc
   {-# MINIMAL conc | cnoc #-}

-- | Suppose we have a nominal abstraction @x' :: KNom s a@ where @a@ has its own internal notion of name restriction.
-- Then @cnoc () x'@ folds the 'KNom'-bound names down into @x'@ to return a concrete element of @a@.
--
-- > cnoc () = unNom
instance (Typeable s, Swappable a, KRestrict s a) => BinderConc (KNom s a) [KAtom s] a s () where
   cnoc = const unNom

-- | Suppose we have a nominal abstraction @x' :: KNom s a@.
-- Then @x' `conc` (Proxy :: Proxy s)@ triggers an IO action to strip the 'KNom' context and concrete @x'@ at some choice of fresh atoms.  
--
-- > cnoc (Proxy :: Proxy s) = exit 
instance (Typeable s, Swappable a) => BinderConc (KNom s a) [KAtom s] a s (Proxy s) where
   cnoc = const exit

-- | Concrete a nominal abstraction at a particular list of atoms.
-- Dangerous for two reasons:
--
-- * The list needs to be long enough.
-- * There are no guarantees about the order the bound atoms come out in.
instance (Typeable s, Swappable a) => BinderConc (KNom s a) [KAtom s] a s [KAtom s] where
   conc a' st = a' @@! \atms a -> perm (zip st atms) a


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