spectacle-1.0.0: src/Language/Spectacle/Syntax/Closure.hs
-- | Closures and variable relations.
--
-- @since 1.0.0
module Language.Spectacle.Syntax.Closure
( Closure (Closure),
Effect (Close),
(.=),
runActionClosure,
)
where
import Data.Coerce (coerce)
import Data.Void (absurd)
import Data.Functor.Loom (hoist, runLoom, (~>~))
import Data.Type.Rec (Has, Name)
import qualified Data.Type.Rec as Rec
import Language.Spectacle.Exception.RuntimeException (RuntimeException)
import Language.Spectacle.Lang
( Effect,
Lang (Op, Pure, Scoped),
Member (project, projectS),
Members,
decomposeOp,
decomposeS,
scope,
)
import Language.Spectacle.RTS.Registers
( RuntimeState (newValues),
StateFun,
Thunk (Evaluated, Thunk, Unchanged),
getRegister,
setThunk,
)
import Language.Spectacle.Syntax.Closure.Internal (Closure (Closure), Effect (Close))
import Language.Spectacle.Syntax.Env (Env, gets, modify)
import Language.Spectacle.Syntax.Error (Error)
import Language.Spectacle.Syntax.NonDet (NonDet)
import Language.Spectacle.Syntax.Prime (RuntimeState (primes), substitute)
-- ---------------------------------------------------------------------------------------------------------------------
-- | The ('.=') operator relates the variable @s@ to the primed values it can access in the next temporal frame. For
-- example, a relation which increments a variable named "x" with any number 1 through 5 each frame of time would be
-- written as:
--
-- @
-- increment :: Action IncrementSpec Bool
-- increment = do
-- x <- plain #x -- retrieve the value of "x" from the previous frame
-- exists [1 .. 5] \n -> do
-- #x .= return (x + n) -- set the value of "x" in the next value to x + n for some number 1 <= n <= 5
-- return True -- relate the value to the next frame, @return (odd n)@ could be written to exclude even n.
-- @
--
-- @since 1.0.0
infix 4 .=
(.=) :: (Member Closure effs, Has s a ctx) => Name s -> StateFun ctx a -> Lang ctx effs ()
name .= expr = scope (Close name expr)
{-# INLINE (.=) #-}
-- | Discharges a 'Closure' effect, returning a 'Rec' new values for each variable in @ctx@.
--
-- @since 1.0.0
runActionClosure ::
Members '[NonDet, Env, Error RuntimeException] effs =>
Lang ctx (Closure ': effs) a ->
Lang ctx effs a
runActionClosure m = evaluateThunks (makeThunks m)
{-# INLINE runActionClosure #-}
-- | Evaluates all unevaluated closures in a specification.
--
-- @since 1.0.0
evaluateThunks ::
Members '[NonDet, Env, Error RuntimeException] effs =>
Lang ctx (Closure ': effs) a ->
Lang ctx effs a
evaluateThunks = \case
Pure x -> pure x
Op op k -> case decomposeOp op of
Left other -> Op other k'
Right bottom -> absurd (coerce bottom)
where
k' = evaluateThunks . k
Scoped scoped loom -> case decomposeS scoped of
Left other -> Scoped other loom'
Right (Close name _) ->
gets (getRegister name . primes) >>= \case
Thunk expr -> do
x <- substitute name expr
modify \rtst -> rtst {newValues = Rec.set name x (newValues rtst)}
runLoom loom' (pure ())
Evaluated x -> do
modify \rtst -> rtst {newValues = Rec.set name x (newValues rtst)}
runLoom loom' (pure ())
Unchanged -> runLoom loom' (pure ())
where
loom' = loom ~>~ hoist evaluateThunks
-- | Traverses 'Lang' collecting all closures as 'Thunks' which will subsequently be evaluated
-- by 'evaluateThunks'. An extra pass is needed to register all closures before substitution of
-- primed variables takes place.
--
-- @since 1.0.0
makeThunks ::
Members '[Closure, Env, Error RuntimeException] effs =>
Lang ctx effs a ->
Lang ctx effs a
makeThunks = \case
Pure x -> pure x
Op op k -> case project op of
Nothing -> Op op k'
Just (Closure bottom :: Closure x) -> absurd bottom
where
k' = makeThunks . k
Scoped scoped loom -> case projectS scoped of
Nothing -> Scoped scoped loom'
Just (Close name expr) -> do
modify \rtst -> rtst {primes = setThunk name expr (primes rtst)}
scope (Close name expr)
runLoom loom' (pure ())
where
loom' = loom ~>~ hoist makeThunks