-- This module implements /explicit closures/ as described in [2] below.
-- It makes extensive use of Template Haskell, and due to TH's stage
-- restrictions, internals like the actual closure representation are
-- delegated to module 'Control.Parallel.HdpH.Closure.Internal'.
--
-- Author: Patrick Maier
-----------------------------------------------------------------------------

-- | Explicit Closures, inspired by [1] and refined by [2].
--
-- References:
--
-- (1) Epstein, Black, Peyton-Jones. /Towards Haskell in the Cloud/.
--     Haskell Symposium 2011.
--
-- (2) Maier, Trinder. /Implementing a High-level Distributed-Memory/
--     /Parallel Haskell in Haskell/. IFL 2011.
--

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Control.Parallel.HdpH.Closure
  ( -- * Explicit Closures
    -- ** Key facts
    -- $KeyFacts

    -- ** The Closure type constructor
    Closure,

    -- ** Caveat: Deserialisation is not type safe
    -- $Deserialisation

    -- ** Closure elimination
    unClosure,

    -- ** Forcing Closures
    forceClosure,

    -- ** Categorical operations on function Closures
    -- | A /function Closure/ is a Closure of type @'Closure' (a -> b)@.
    idC,
    termC,
    compC,
    apC,

    -- ** Construction of value Closures
    -- | A /value Closure/ is a Closure, which when eliminated (from its 
    -- serialisable representation, at least) yields an evaluated value 
    -- (rather than an unevaluated thunk).
    toClosure,
    ToClosure(
      locToClosure
    ),
    StaticToClosure,
    staticToClosure,

    -- ** Safe Closure construction
    mkClosure,
    mkClosureLoc,
    LocT,
    here,

    -- ** Unsafe Closure construction
    unsafeMkClosure,

    -- * Static terms
    -- | A term @t@ is called /static/ if it could be declared at the toplevel.
    -- That is, @t@ is static if all free variables occuring in @t@ are 
    -- toplevel.

    -- ** The Static type constructor
    Static,

    -- ** Static declaration and registration
    StaticDecl,
    declare,
    register,
    showStaticTable,

    -- ** Static deserialisers
    -- | A @'Static'@ /environment deserialiser/ is a term of type
    -- @'Static' ('Env' -> a)@, for some type @a@, and is part
    -- of the serialisable representation of an explicit Closure.
    static,
    static_,
    staticLoc,
    staticLoc_,

    -- * Serialised environment
    -- | A /serialised enviroment/ is part of the serialisable representation
    -- of an explicit Closure.
    Env,
    encodeEnv,
    decodeEnv,

    -- * This module's Static declaration
    declareStatic

    -- * Tutorial on safe Closure construction
    -- $Tutorial
  ) where

import Prelude
import Control.DeepSeq (NFData, deepseq)
import Data.Monoid (mconcat)
import Data.Serialize (Serialize)

import Control.Parallel.HdpH.Closure.Internal   -- re-export whole module
import Control.Parallel.HdpH.Closure.Static
       (Static, StaticDecl, declare, register, showStaticTable)


-----------------------------------------------------------------------------
-- $KeyFacts
--
-- An /explicit Closure/ is a term of type @Closure t@, wrapping a thunk
-- of type @t@. Henceforth, we will write /Closure/ (capitalised) to mean 
-- an explicit Closure, and /closure/ (in lower case) to mean an ordinary
-- Haskell closure (ie. a thunk).
--
-- The @'Closure'@ type constructor is abstract (see reference [2] or
-- module 'Control.Parallel.HdpH.Closure.Internal' for its internal /dual/
-- representation).
--
-- The function @'unClosure'@ returns the thunk wrapped in a Closure.
--
-- The function @'unsafeMkClosure'@ constructs Closures but is considered
-- /unsafe/ because it exposes the internal dual representation, and
-- relies on the programmer to guarantee consistency.
--
-- Article [2] proposes a /safe/ Closure construction @$(mkClosure [|...|])@
-- where the @...@ is an arbitrary thunk. Due to current limitations of the
-- GHC (namely missing support for the @'Static'@ type constructor),
-- this module provides only limited variants of @$(mkClosure [|...|])@.
-- The restrictions on the the thunk @...@ are
--
-- * either @...@ is a toplevel variable (also called a /static closure/),
--
-- * or @...@ is a toplevel variable (also called a /closure abstraction/)
--   applied to a tuple of local variables.
--
-- As a matter of nomenclature, functions operating on Closures will either
-- contain the string @Closure@ (like @'unClosure'@ and @'mkClosure'@) or will
-- end in the suffix @C@ (like the categorical operations @'idC'@ and
-- @'compC'@).
--
-- Some identities involving Closures:
--
-- (1) @unClosure $ unsafeMkClosure x fun env = x@
--
-- (2) @unClosure $ toClosure x = x@
--
-- (3) @unClosure $(mkClosure [| stat_clo |]) = stat_clo@
--
-- (4) @unClosure $(mkClosure [| clo_abs free_vars |]) = clo_abs free_vars@
--


-----------------------------------------------------------------------------
-- $Deserialisation
--
-- Deserialising a serialised value via the methods of class
-- @'Data.Binary.Binary'@ (or @'Data.Serialize.Serialize'@) is not type safe.
-- For instance, the compiler will happily assign type @Int -> Bool@ to
--
-- > decodeBool . encodeInt where
-- >     decodeBool = decode :: ByteString -> Bool
-- >     encodeInt  = encode :: Int -> ByteString
--
-- This type coercion will only fail at runtime, when (and if) the decoder for
-- @Bool@ stumbles over unexepected input values. Hence deserialising can be 
-- viewed as an assertion that the serialised byte string represents a value
-- of the type @decode@ expects to see. A well-written decoder will check
-- this assertion on deserialisation, and will abort with an error message
-- if the assertion fails.
--
-- HdpH treats Closure deserialisation similarly as an assertion that the
-- serialised byte string represents a value of the expected Closure type.
-- However, HdpH does not check whether the assertion holds. Instead it
-- subverts the type system via @unsafeCoerce@, with potentially disastrous
-- consequences (like seg faults) in cases where the assertion fails.
--
-- The up side of this design choice is a simpler Closure representation
-- without runtime type reflection.
-- The down side is that Closure deserialisation has to be treated with
-- /extreme care/.
-- HdpH, for instance, avoids the pitfalls of Closure deserialisation by
-- making sure only Closures of the fixed type @Closure (Par ())@ are
-- serialised and deserialised.


-----------------------------------------------------------------------------
-- $Tutorial
--
-- This guide will demonstrate the safe construction of explicit Closures
-- in HdpH through a series of examples.
--
--
-- [Constructing plain Closures]
--
-- The first example is the definition of the Closure transformation
-- @apC@. It is defined in [2] (where it is called @mapClosure@) by eliminating
-- the Closures of its arguments, and constructing a new Closure of the the
-- resulting application, as follows:
--
-- > apC :: Closure (a -> b) -> Closure a -> Closure b
-- > apC clo_f clo_x =
-- >   $(mkClosure [|unClosure clo_f $ unClosure clo_x|])
--
-- If @'Static'@ were fully supported by GHC then @'mkClosure'@ would abstract
-- the free local variables (here @clo_f@ and @clo_x@) in its quoted argument,
-- serialise a tuple of these variables, and construct a suitable @'Static'@
-- deserialiser. In short, expanding the above Template Haskell splice
-- would yield the following definition:
--
-- > apC clo_f clo_x =
-- >   let thk = unClosure clo_f $ unClosure clo_x
-- >       env = encodeEnv (clo_f, clo_x)
-- >       fun = static $ \ env -> let (clo_f, clo_x) = decodeEnv env
-- >                                 in unClosure clo_f $ unClosure clo_x
-- >     in unsafeMkClosure thk fun env
--
-- However, the current implementation of @'mkClosure'@ cannot do this because
-- there is no term former @static@. Instead, the current implementation
-- of @'mkClosure'@ expects its quoted argument to be in a one of two special
-- forms: either it is a single /toplevel/ variable, or an application of a
-- /toplevel/ variable to a tuple of free variables. To distinguish the two
-- forms, Closures constructed from single toplevel variables will be called
-- /static/ (because like static terms they do not capture any variables).
--
-- Here is the definition of @apC@ as an example of how to construct a
-- general, non-static Closure.
--
-- > apC clo_f clo_x =
-- >   $(mkClosure [|apC_abs (clo_f, clo_x)|])
-- >
-- > apC_abs :: (Closure (a -> b), Closure a) -> b
-- > apC_abs (clo_f, clo_x) = unClosure clo_f $ unClosure clo_x
--
-- First, the programmer must manually define a toplevel /closure abstraction/
-- @apC_abs@ which abstracts a tuple @(clo_f, clo_x)@ of free local variables
-- occuring in the expression to be converted into a Closure. (Usually, the
-- programmer would want the closure abstraction @apC_abs@ to be inlined.)
--
-- Second, the programmer constructs the explicit Closure via a Template 
-- Haskell splice @$(mkClosure [|apC_abs (clo_f, clo_x)|])@, where the quoted 
-- expression @apC_abs (clo_f, clo_x)@ is an application of the toplevel closure
-- abstraction to a tuple of free local variables. It is important
-- that this actual tuple of variables matches /exactly/ the formal tuple
-- in the definition of the closure abstraction. In fact, best practice is
-- for the quoted expression to textually match the left-hand side of the
-- definition of the closure abstraction.
--
-- Finally, a @'Static'@ deserialiser corresponding to the toplevel closure
-- abstraction must be declared. To this end, this module exports a Template
-- Haskell function @'static'@ which converts the name of a toplevel closure
-- abstraction into a @'Static'@ deserialiser, and a function @'declare'@ which
-- turns the @'Static'@ deserialiser into a @'Static'@ declaration. These 
-- should be used as follows; see also the definition of @declareStatic@ below.
--
-- > declare $(static 'apC_abs)
--
--
-- The second example is the definition of the static Closure @idC@, which
-- lifts the identity function to a function Closure.
-- The definition is a follows; see also code towards the end of this file.
--
-- > idC :: Closure (a -> a)
-- > idC = $(mkClosure [|id|])
--
-- The expression to be converted into a Closure, the variable @'id'@, does not
-- contain any free local variables, so there is no need to abstract a tuple
-- of free variables. In fact, the expression is already a toplevel variable,
-- so there is also no need to define a new toplevel closure abstraction either.
-- Thus, the programmer constructs the static Closure via the the Template
-- Haskell splice @$(mkClosure [|id|])@, where the quoted expression @'id'@
-- is a toplevel variable.
--
-- The programmer must also declare a @'Static'@ deserialiser corresponding to
-- the toplevel variable of which the static Closure was created. This is done
-- as follows; see also the definition of @declareStatic@ below. 
--
-- > declare $(static_ 'id)
--
-- Note the use of @'static_'@ instead of @'static'@ to create a @'Static'@
-- deserialiser for a /static/ Closure, ie. a @'Static'@ deserialiser that does
-- not actually deserialise any free variables. Accidentally mixing up
-- @'static'@ and @'static_'@ may lead to runtime errors!
--
--
-- [Constructing families of Closures]
--
-- A problem arises with Closures whose type is not only polymorphic (as eg.
-- the type of @idC@ above) but also constrained by type classes. The problem
-- here is that the class constraint has to be resolved statically, as there
-- is no way of attaching implicit dictionaries to a Closure (because these
-- dictionaries would have to be serialised as well). However, there is a
-- way of safely constructing such constrained Closures. The idea is to
-- actually construct a family of Closures, indexed by source locations.
-- The indices are produced by certain type class instances, effectively
-- turning the location-based into a type-based indexing. We illustrate
-- this method on two examples.
--
-- The first example is the function @toClosure@ which converts any suitable
-- value into a Closure, forcing the value upon serialisation. The /suitable/
-- values are those whose type is an instance of class
-- @'Data.Serialize.Serialize'@, so one would expect @toClosure@ to have the 
-- following implementation and @'Static'@ declaration:
--
-- > toClosure :: (Serialize a) => a -> Closure a
-- > toClosure val = $(mkClosure [| id val |])
-- >
-- > declare $(static 'id)
--
-- However, this does not compile - the last line complains from an ambiguous
-- type variable @a@ in the constrait @Serialize a@.
--
-- The solution is to define a new type class @ToClosure@ as a subclass of
-- the given @'Data.Serialize.Serialize'@ constraint, and use instances of
-- @ToClosure@ to index a family of Closures. The indexing is done by 
-- @locToClosure@, the only member of class @ToClosure@, as follows.
--
-- > class (Serialize a) => ToClosure a where
-- >   locToClosure :: LocT a
-- >
-- > toClosure :: (ToClosure a) => a -> Closure a
-- > toClosure val = $(mkClosureLoc [| id val |]) locToClosure
--
-- Note that the above splice @$(mkClosureLoc [|id val|])@ generates a family
-- of type @'LocT' a -> 'Closure' a@. Applying that family to the index
-- @locToClosure@ yields the actual Closure. On the face of it, indexing
-- appears to be location based, but actually the family generated by
-- @'mkClosureLoc'@ never evaluates the index; thanks to the phantom type
-- argument of @'LocT'@ the indexing is really done statically on the types.
-- (Though the location information is necessary to tag the associated
-- @'Static'@ deserialisers, and will be evaluated when constructing the
-- @'Static'@ table.)
--
-- What this achieves is reducing the constraint on @toClosure@ from
-- @'Data.Serialize.Serialize'@ to @ToClosure@. Hence @toClosure@ is only
-- available for types for which the programmer explicitly instantiates 
-- @ToClosure@. These instances are very simple, see the following two samples.
--
-- > instance ToClosure Int where locToClosure = $(here)
-- > instance ToClosure (Closure a) where locToClosure = $(here)
--
-- Note that the two instances are entirely identical, in fact all instances
-- of @ToClosure@ must be identical. In particular, instances must not
-- be recursive, so that the number of types instantiating @ToClosure@
-- matches exactly the number of instance declarations in the source code.
-- All an instance does is record its location in the source code via
-- @locToClosure@, making @locToClosure@ a key for @ToClosure@ instances.
--
-- The programmer must declare the @'Static'@ deserialisers associated with
-- the above family of Closures. More precisely, she must declare one
-- deserialiser per @ToClosure@ instance. This is done by defining a
-- family of @'Static'@ deserialisers, similar to the family of Closures.
--
-- > type StaticToClosure a = Static (Env -> a)
-- >
-- > staticToClosure :: (ToClosure a) => StaticToClosure a
-- > staticToClosure = $(staticLoc 'id) locToClosure
--
-- Note that the splice @$(staticLoc 'id)@ above generates a family of
-- type @'LocT' a -> 'Static' ('Env' -> a)@. Applying that family to the index
-- @locToClosure@ yields an actual @'Static'@ deserialiser. The type synomyn
-- @StaticToClosure@ is a mere convenience to improve readability, as will
-- become evident when actually declaring the @'Static'@ deserialisers
-- (particularly in the second example).
--
-- It remains to actually declare the @'Static'@ deserialisers, one per instance
-- of @ToClosure@. Since @'Static'@ declarations form a monoid, the programmer
-- can simply concatenate all these declarations. So, given the above sample
-- @ToClosure@ instances, the programmer would declare the associated
-- deserialisers as follows.
--
-- > Data.Monoid.mconcat
-- >   [declare (staticToClosure :: StaticToClosure Int),
-- >    declare (staticToClosure :: forall a . StaticToClosure (Closure a))]
--
--
-- The second example of families of Closures is @forceCC@, which wraps
-- @forceC@ into a Closure, where @forceC@ is defined as follows:
--
-- > forceC :: (NFData a, ToClosure a) => Strategy (Closure a)
-- > forceC clo = return $! forceClosure clo
--
-- Note that @forceC@ lifts @'forceClosure'@ to a /strategy/ in the @Par@
-- monad; please see module @Control.Parallel.HdpH.Strategies@ for the
-- definition of the @Strategy@ type constructor.
--
-- Ideally, @forceCC@ would be implemented simply like this:
--
-- > forceCC :: (NFData a, ToClosure a) => Closure (Strategy (Closure a))
-- > forceCC = $(mkClosure [|forceC|])
--
-- However, the type class constraint demands that @forceCC@ generate a family
-- of Closures; in fact, it must generate a family of /static/ Closures because 
-- @forceC@, the expression quoted in the Template Haskell splice, is a single
-- toplevel variable. The actual implementation of @forceCC@ is as follows:
--
-- > class (NFData a, ToClosure a) => ForceCC a where
-- >   locForceCC :: LocT (Strategy (Closure a))
-- >
-- > forceCC :: (ForceCC a) => Closure (Strategy (Closure a))
-- > forceCC = $(mkClosureLoc [| forceC |]) locForceCC
--
-- Above is the first part of the code, the definition of a new type class
-- @ForceCC@ and the defintion of @forceCC@ itself. Note the complex phantom
-- type argument of @locForceCC@; this is dictated by the splice
-- @$(mkClosureLoc [|forceC|])@ which is of type
-- @'LocT' (Strategy ('Closure' a)) -> 'Closure' (Strategy ('Closure' a))@.
-- Instances of class @ForceCC@ are very similar to instances of @ToClosure@,
-- cf. the following two samples.
--
-- > instance ForceCC Int where locForceCC = $(here)
-- > instance ForceCC (Closure a) where locForceCC = $(here)
--
-- Along with the family of Closures there is a family of @'Static'@
-- deserialisers, defined as follows. Note the use of @'staticLoc_'@ because
-- @'forceCC'@ generates a family of /static/ Closures.
--
-- > type StaticForceCC a = Static (Env -> Strategy (Closure a))
-- >
-- > staticForceCC :: (ForceCC a) => StaticForceCC a
-- > staticForceCC = $(staticLoc_ 'forceC) locForceCC
--
-- It remains to actually declare the @'Static'@ deserialisers, one per
-- @ForceCC@ instance. Again, this is very similar to declaring the
-- deserialisers linked to @ToClosure@ instances. But note how the type
-- synonym @StaticForceCC@ improves readability of the declaration - just try
-- expanding the last line of the following declaration.
--
-- > Data.Monoid.mconcat
-- >   [declare (staticForceCC :: StaticForceCC Int),
-- >    declare (staticForceCC :: forall a . StaticForceCC (Closure a))]
--
--
-- [Registering @'Static'@ deserialisers]
--
-- All @'Static'@ deserialisers need to be declared, as shown above.
-- By convention, each module must concatenate all such @'Static'@ declarations
-- (including declarations in imported modules) into a single @'Static'@
-- declaration. The @Main@ module, finally, must /register/ its @'Static'@
-- declaration (which by convention includes all declarations in imported
-- modules) at the beginning of the @main@ function, to create the @'Static'@
-- table. This section shows how to concatenate and register @'Static'@
-- declarations.
--
-- First, concatenating @'Static'@ declarations. As shown above, a @'Static'@
-- deserialiser can be turned into a @'Static'@ declaration (of type
-- @'StaticDecl'@) by applying @'declare'@. The type @'StaticDecl'@ is an
-- instance of class @'Data.Monoid.Monoid'@, so @'Static'@ declarations can be
-- concatenated via the @'mconcat'@ operation. In fact, @'StaticDecl'@ is not
-- just a monoid but a commutative and idempotent monoid, thanks to the
-- way @'Static'@ deserialisers are constructed. That means that @'Static'@
-- declarations can be concatenated repeatedly and in any order without
-- changing the result.
--
-- As a matter of convention, every module which constructs explicit Closures
-- must export a term @declareStatic :: StaticDecl@, a comprehensive declaration
-- of all the module's @'Static'@ deserialisers, including all @'Static'@
-- deserialisers of imported modules. The latter is required because the module
-- may call  imported functions which in turn depend on their modules'
-- @'Static'@ deserialisers.
--
-- > declareStatic :: StaticDecl
-- > declareStatic = Data.Monoid.mconcat
-- >   [declare $(static_ 'id),
-- >    declare $(static_ 'constUnit),
-- >    declare $(static 'compC_abs),
-- >    declare $(static 'apC_abs),
-- >    declare (staticToClosure :: forall a . StaticToClosure (Closure a))]
--
-- Above is a simple example of such a comprehensive Static declaration,
-- the declaration of this module. Note that the order of the list argument
-- to @'mconcat'@ does not matter because because @'StaticDecl'@ is a
-- commutative monoid. Below is a more complex example, taken from a HdpH
-- program.
--
-- > declareStatic :: StaticDecl
-- > declareStatic = Data.Monoid.mconcat
-- >   [Control.Parallel.HdpH.declareStatic,
-- >    Control.Parallel.HdpH.Strategies.declareStatic,
-- >    declare (staticToClosure :: StaticToClosure Int),
-- >    declare (staticToClosure :: StaticToClosure [Int]),
-- >    declare (staticToClosure :: StaticToClosure Integer),
-- >    declare (staticForceCC :: StaticForceCC Integer),
-- >    declare $(static 'spark_sum_euler_abs),
-- >    declare $(static_ 'sum_totient),
-- >    declare $(static_ 'totient)]
--
-- This declaration does concatenate the Static declarations of two imported
-- modules, @Control.Parllel.HdpH@ and @Control.Parallel.HdpH.Strategies@.
-- Actually, the latter also imports the former, so
-- @Control.Parallel.HdpH.Strategies.declareStatic@ already concatenates
-- @Control.Parallel.HdpH.declareStatic@, and so the above call to
-- @Control.Parallel.HdpH.declareStatic@ could be omitted. However, that
-- omission is only justified in the knowledge of implementation details of
-- @Control.Parallel.HdpH.Strategies@ (namely its import graph). The convention
-- that @'Static'@ declarations of /all/ imported modules must be concatenated,
-- regardless of whether they are transitively concatenated by other imported
-- modules, is more robust; the resulting @'Static'@ declaration is the same
-- anyway, thanks to @'StaticDecl'@ being an idempotent monoid.
--
-- A general rule how to deal with imports and @'Static'@ declarations:
-- If module @M1@ imports another module @M2@, and
-- if @M2@ exports a variable @declareStatic :: StaticDecl@
-- then @M1@ must export its own @declareStatic :: StaticDecl@,
-- which needs to concatenate @M2.declareStatic@ (amongst other declarations).
-- Note that the rule applies even if @M1@ ostensibly imports "nothing"
-- from @M2@, eg. @M1@ imports @M2@ with the clause @import M2 ()@. For
-- this clause may still import instance declarations from @M2@, and those
-- instances may depend on M2's @'Static'@ deserialisers.
--
--
-- Finally, registering a @'Static'@ declaration. This must be done exactly once
-- in the @Main@ module, right at the start of @main@, by calling the function
-- @'register'@ on the @Main@ module's comprehensive @'Static'@ declaration.
-- This is shown in sample code below, taken from a HdpH program.
-- Note that any actions executed prior to @'register'@ (ie. the @hSetBuffering@
-- calls) must not involve explicit Closures.
--
-- > main :: IO ()
-- > main = do
-- >   hSetBuffering stdout LineBuffering
-- >   hSetBuffering stderr LineBuffering
-- >   register declareStatic
-- >   ...


-----------------------------------------------------------------------------
-- Static declaration

-- 'ToClosure' instance for Closures, only recording indexing location;
-- all instances of 'ToClosure' look like this.
instance ToClosure (Closure a) where locToClosure = $(here)

declareStatic :: StaticDecl
declareStatic = mconcat
  [declare $(static_ 'id),
   declare $(static_ 'constUnit),
   declare $(static 'compC_abs),
   declare $(static 'apC_abs),
   declare (staticToClosure :: forall a . StaticToClosure (Closure a))]
     -- Declaration of indexed Static deserialiser for 'toClosure';
     -- all declarations of 'staticToClosure' look like this.


-----------------------------------------------------------------------------
-- Value Closure construction

-- | @toClosure x@ constructs a value Closure wrapping @x@, provided the
-- type of @x@ is an instance of class @'ToClosure'@.
-- Note that the serialised representation of the resulting Closure stores a
-- serialised representation (as per class @'Data.Serialize.Serialize'@) of
-- @x@, so serialising the resulting Closure will force @x@ (hence could be
-- costly). However, Closure construction itself is cheap.
toClosure :: (ToClosure a) => a -> Closure a
toClosure val = $(mkClosureLoc [| id val |]) locToClosure

-- | Indexing class, recording types which support the @'toClosure'@ operation;
-- see the tutorial below for a more thorough explanation.
-- Note that @ToClosure@ is a subclass of @'Data.Serialize.Serialize'@.
class (Serialize a) => ToClosure a where
  -- | Only method of class @ToClosure@, recording the source location
  -- where an instance of @ToClosure@ is declared.
  locToClosure :: LocT a

-- | Type synonym for declaring the @'Static'@ deserialisers required by
-- @'ToClosure'@ instances; see the tutorial below for a more thorough 
-- explanation.
type StaticToClosure a = Static (Env -> a)

-- | @'Static'@ deserialiser required by a @'ToClosure'@ instance;
-- see the tutorial below for a more thorough explanation.
staticToClosure :: (ToClosure a) => StaticToClosure a
staticToClosure = $(staticLoc 'id) locToClosure


-----------------------------------------------------------------------------
-- fully forcing Closures

-- | @forceClosure@ fully forces its argument, ie. fully normalises the thunk.
-- Importantly, @forceClosure@ also updates the serialisable closure
-- represention in order to keep it in sync with the normalised thunk.
-- Note that only the thunk of the resulting Closure is in normal form;
-- for the serialisable representation to also be in normal form, the resulting
-- Closure must be forced by @'deepseq'@.
--
-- Note that @forceClosure clo@ does not have the same effect as
--
-- * @'Control.DeepSeq.force' clo@ (because @forceClosure@ updates the closure
--   representation), or
--
-- * @'Control.DeepSeq.force' $ toClosure $ unClosure clo@ (because
--   @forceClosure@ does not force the resulting Closure's serialisable
--   representation).
--
forceClosure :: (NFData a, ToClosure a) => Closure a -> Closure a
forceClosure clo = x `deepseq` toClosure x where x = unClosure clo

-- Note that it does not make sense to construct a variant of @forceClosure@
-- that would evaluate the thunk inside a Closure head-strict only. The reason 
-- is that serialising such a Closure would turn it into a fully forced one.


------------------------------------------------------------------------------
-- Categorical operations on function Closures

-- | Identity arrow wrapped in a Closure.
idC :: Closure (a -> a)
idC = $(mkClosure [| id |])


-- | Terminal arrow wrapped in a Closure.
termC :: Closure (a -> ())
termC = $(mkClosure [| constUnit |])

{-# INLINE constUnit #-}
constUnit :: a -> ()
constUnit = const ()


-- | Composition of function Closures.
compC :: Closure (b -> c) -> Closure (a -> b) -> Closure (a -> c)
compC clo_g clo_f = $(mkClosure [| compC_abs (clo_g, clo_f) |])

{-# INLINE compC_abs #-}
compC_abs :: (Closure (b -> c), Closure (a -> b)) -> (a -> c)
compC_abs (clo_g, clo_f) = unClosure clo_g . unClosure clo_f


-- | Application of a function Closure to another Closure.
-- Behaves like the @ap@ operation of an applicative functor.
apC :: Closure (a -> b) -> Closure a -> Closure b
apC clo_f clo_x = $(mkClosure [| apC_abs (clo_f, clo_x) |])

{-# INLINE apC_abs #-}
apC_abs :: (Closure (a -> b), Closure a) -> b
apC_abs (clo_f, clo_x) = unClosure clo_f $ unClosure clo_x