idris-0.99.1: libs/contrib/Control/ST.idr
module Control.ST
import Language.Reflection.Utils
%default total
infix 5 :::
{- A resource is a pair of a label and the current type stored there -}
public export
data Resource : Type where
MkRes : label -> Type -> Resource
%error_reverse
public export
(:::) : label -> Type -> Resource
(:::) = MkRes
export
data Var = MkVar -- Phantom, just for labelling purposes
{- Contexts for holding current resources states -}
namespace Context
public export
data Context : Type where
Nil : Context
(::) : Resource -> Context -> Context
public export
(++) : Context -> Context -> Context
(++) [] ys = ys
(++) (x :: xs) ys = x :: xs ++ ys
{- Proof that a label has a particular type in a given context -}
public export
data InState : lbl -> Type -> Context -> Type where
Here : InState lbl st (MkRes lbl st :: rs)
There : InState lbl st rs -> InState lbl st (r :: rs)
{- Update an entry in a context with a new state -}
public export
updateCtxt : (ctxt : Context) ->
InState lbl st ctxt -> Type -> Context
updateCtxt (MkRes lbl _ :: rs) Here val = (MkRes lbl val :: rs)
updateCtxt (r :: rs) (There x) ty = r :: updateCtxt rs x ty
{- Remove an entry from a context -}
public export
drop : (ctxt : Context) -> (prf : InState lbl st ctxt) ->
Context
drop (MkRes lbl st :: rs) Here = rs
drop (r :: rs) (There p) = r :: drop rs p
{- Proof that a resource state (label/type) is in a context -}
public export
data ElemCtxt : Resource -> Context -> Type where
HereCtxt : ElemCtxt a (a :: as)
ThereCtxt : ElemCtxt a as -> ElemCtxt a (b :: as)
public export %error_reduce
dropEl : (ys: _) -> ElemCtxt x ys -> Context
dropEl (x :: as) HereCtxt = as
dropEl (x :: as) (ThereCtxt p) = x :: dropEl as p
{- Proof that a variable name is in a context -}
public export
data VarInCtxt : Var -> Context -> Type where
VarHere : VarInCtxt a (MkRes a st :: as)
VarThere : VarInCtxt a as -> VarInCtxt a (b :: as)
public export %error_reduce
dropVarIn : (ys: _) -> VarInCtxt x ys -> Context
dropVarIn ((MkRes x _) :: as) VarHere = as
dropVarIn (x :: as) (VarThere p) = x :: dropVarIn as p
public export
data Composite : List Type -> Type where
CompNil : Composite []
CompCons : (x : a) -> Composite as -> Composite (a :: as)
namespace VarList
public export
data VarList : List Type -> Type where
Nil : VarList []
(::) : Var -> VarList ts -> VarList (t :: ts)
public export
mkCtxt : VarList tys -> Context
mkCtxt [] = []
mkCtxt {tys = (t :: ts)} (v :: vs) = (v ::: t) :: mkCtxt vs
{- Proof that a context is a subset of another context -}
public export
data SubCtxt : Context -> Context -> Type where
SubNil : SubCtxt [] []
InCtxt : (el : ElemCtxt x ys) -> SubCtxt xs (dropEl ys el) ->
SubCtxt (x :: xs) ys
Skip : SubCtxt xs ys -> SubCtxt xs (y :: ys)
%hint
public export
subCtxtId : SubCtxt xs xs
subCtxtId {xs = []} = SubNil
subCtxtId {xs = (x :: xs)} = InCtxt HereCtxt subCtxtId
public export
subCtxtNil : SubCtxt [] xs
subCtxtNil {xs = []} = SubNil
subCtxtNil {xs = (x :: xs)} = Skip subCtxtNil
{- Proof that every variable in the list appears once in the context -}
public export
data VarsIn : List Var -> Context -> Type where
VarsNil : VarsIn [] []
InCtxtVar : (el : VarInCtxt x ys) -> VarsIn xs (dropVarIn ys el) ->
VarsIn (x :: xs) ys
SkipVar : VarsIn xs ys -> VarsIn xs (y :: ys)
public export
Uninhabited (ElemCtxt x []) where
uninhabited HereCtxt impossible
uninhabited (ThereCtxt _) impossible
public export %error_reduce
updateWith : (new : Context) -> (xs : Context) ->
SubCtxt ys xs -> Context
-- At the end, add the ones which were updated by the subctxt
updateWith new [] SubNil = new
updateWith new [] (InCtxt el z) = absurd el
-- Don't add the ones which were consumed by the subctxt
updateWith [] (x :: xs) (InCtxt el p)
= updateWith [] (dropEl _ el) p
updateWith (n :: ns) (x :: xs) (InCtxt el p)
= n :: updateWith ns (dropEl _ el) p
-- Do add the ones we didn't use in the subctxt
updateWith new (x :: xs) (Skip p) = x :: updateWith new xs p
public export
getVarType : (xs : Context) -> VarInCtxt v xs -> Type
getVarType ((MkRes v st) :: as) VarHere = st
getVarType (b :: as) (VarThere x) = getVarType as x
public export
getCombineType : VarsIn ys xs -> List Type
getCombineType VarsNil = []
getCombineType (InCtxtVar el y) = getVarType _ el :: getCombineType y
getCombineType (SkipVar x) = getCombineType x
public export
dropCombined : VarsIn vs ctxt -> Context
dropCombined {ctxt = []} VarsNil = []
dropCombined {ctxt} (InCtxtVar el y) = dropCombined y
dropCombined {ctxt = (y :: ys)} (SkipVar x) = y :: dropCombined x
public export
combineVarsIn : (ctxt : Context) -> VarsIn (comp :: vs) ctxt -> Context
combineVarsIn {comp} ctxt (InCtxtVar el x)
= ((comp ::: Composite (getCombineType x)) :: dropCombined (InCtxtVar el x))
combineVarsIn (y :: ys) (SkipVar x) = y :: combineVarsIn ys x
namespace Env
public export
data Env : Context -> Type where
Nil : Env []
(::) : ty -> Env xs -> Env ((lbl ::: ty) :: xs)
lookupEnv : InState lbl ty ctxt -> Env ctxt -> ty
lookupEnv Here (x :: xs) = x
lookupEnv (There p) (x :: xs) = lookupEnv p xs
updateEnv : (prf : InState lbl ty ctxt) -> Env ctxt -> ty' ->
Env (updateCtxt ctxt prf ty')
updateEnv Here (x :: xs) val = val :: xs
updateEnv (There p) (x :: xs) val = x :: updateEnv p xs val
dropVal : (prf : InState lbl st ctxt) -> Env ctxt -> Env (drop ctxt prf)
dropVal Here (x :: xs) = xs
dropVal (There p) (x :: xs) = x :: dropVal p xs
envElem : ElemCtxt x xs -> Env xs -> Env [x]
envElem HereCtxt (x :: xs) = [x]
envElem (ThereCtxt p) (x :: xs) = envElem p xs
dropDups : Env xs -> (el : ElemCtxt x xs) -> Env (dropEl xs el)
dropDups (y :: ys) HereCtxt = ys
dropDups (y :: ys) (ThereCtxt p) = y :: dropDups ys p
dropEntry : Env ctxt -> (prf : VarInCtxt x ctxt) -> Env (dropVarIn ctxt prf)
dropEntry (x :: env) VarHere = env
dropEntry (x :: env) (VarThere y) = x :: dropEntry env y
dropVarsIn : Env ctxt -> (prf : VarsIn vs ctxt) -> Env (dropCombined prf)
dropVarsIn [] VarsNil = []
dropVarsIn env (InCtxtVar el z) = dropVarsIn (dropEntry env el) z
dropVarsIn (x :: env) (SkipVar z) = x :: dropVarsIn env z
getVarEntry : Env ctxt -> (prf : VarInCtxt v ctxt) -> getVarType ctxt prf
getVarEntry (x :: xs) VarHere = x
getVarEntry (x :: env) (VarThere p) = getVarEntry env p
mkComposite : Env ctxt -> (prf : VarsIn vs ctxt) -> Composite (getCombineType prf)
mkComposite [] VarsNil = CompNil
mkComposite env (InCtxtVar el z)
= CompCons (getVarEntry env el) (mkComposite (dropEntry env el) z)
mkComposite (x :: env) (SkipVar z) = mkComposite env z
rebuildVarsIn : Env ctxt -> (prf : VarsIn (comp :: vs) ctxt) ->
Env (combineVarsIn ctxt prf)
rebuildVarsIn env (InCtxtVar el p)
= mkComposite (dropEntry env el) p :: dropVarsIn env (InCtxtVar el p)
rebuildVarsIn (x :: env) (SkipVar p) = x :: rebuildVarsIn env p
{- Some things to make STrans interfaces look prettier -}
infix 6 :->
public export
data Action : Type -> Type where
Stable : lbl -> Type -> Action ty
Trans : lbl -> Type -> (ty -> Type) -> Action ty
Remove : lbl -> Type -> Action ty
Add : (ty -> Context) -> Action ty
namespace Stable
public export %error_reduce
(:::) : lbl -> Type -> Action ty
(:::) = Stable
namespace Trans
public export
data Trans ty = (:->) Type Type
public export %error_reduce
(:::) : lbl -> Trans ty -> Action ty
(:::) lbl (st :-> st') = Trans lbl st (const st')
namespace DepTrans
public export
data DepTrans ty = (:->) Type (ty -> Type)
public export %error_reduce
(:::) : lbl -> DepTrans ty -> Action ty
(:::) lbl (st :-> st') = Trans lbl st st'
public export
or : a -> a -> Either b c -> a
or x y = either (const x) (const y)
public export
add : Type -> Action a
add ty = Add (\var => [var ::: ty])
public export
addIfRight : Type -> Action (Either a b)
addIfRight ty = Add (either (const []) (\var => [var ::: ty]))
public export
addIfJust : Type -> Action (Maybe a)
addIfJust ty = Add (maybe [] (\var => [var ::: ty]))
public export
kept : SubCtxt xs ys -> Context
kept SubNil = []
kept (InCtxt el p) = kept p
kept (Skip {y} p) = y :: kept p
-- We can only use new/delete/read/write on things wrapped in State. Only an
-- interface implementation should know that a thing is defined as State,
-- so it's the only thing that's able to peek at the internals
public export
data State : Type -> Type where
Value : ty -> State ty
export
data STrans : (m : Type -> Type) ->
(ty : Type) ->
Context -> (ty -> Context) ->
Type where
Pure : (result : ty) ->
STrans m ty (out_fn result) out_fn
Bind : STrans m a st1 st2_fn ->
((result : a) ->
STrans m b (st2_fn result) st3_fn) ->
STrans m b st1 st3_fn
Lift : Monad m => m t -> STrans m t ctxt (const ctxt)
New : (val : state) ->
STrans m Var ctxt (\lbl => (lbl ::: state) :: ctxt)
Delete : (lbl : Var) ->
(prf : InState lbl st ctxt) ->
STrans m () ctxt (const (drop ctxt prf))
DropSubCtxt : (prf : SubCtxt ys xs) ->
STrans m (Env ys) xs (const (kept prf))
Split : (lbl : Var) ->
(prf : InState lbl (Composite vars) ctxt) ->
STrans m (VarList vars) ctxt
(\ vs => mkCtxt vs ++
updateCtxt ctxt prf (State ()))
Combine : (comp : Var) -> (vs : List Var) ->
(prf : VarsIn (comp :: vs) ctxt) ->
STrans m () ctxt
(const (combineVarsIn ctxt prf))
Call : STrans m t sub new_f -> (ctxt_prf : SubCtxt sub old) ->
STrans m t old (\res => updateWith (new_f res) old ctxt_prf)
Read : (lbl : Var) ->
(prf : InState lbl ty ctxt) ->
STrans m ty ctxt (const ctxt)
Write : (lbl : Var) ->
(prf : InState lbl ty ctxt) ->
(val : ty') ->
STrans m () ctxt (const (updateCtxt ctxt prf ty'))
export
dropEnv : Env ys -> SubCtxt xs ys -> Env xs
dropEnv [] SubNil = []
dropEnv [] (InCtxt idx rest) = absurd idx
dropEnv (z :: zs) (InCtxt idx rest)
= let [e] = envElem idx (z :: zs) in
e :: dropEnv (dropDups (z :: zs) idx) rest
dropEnv (z :: zs) (Skip p) = dropEnv zs p
keepEnv : Env ys -> (prf : SubCtxt xs ys) -> Env (kept prf)
keepEnv env SubNil = env
keepEnv env (InCtxt el prf) = keepEnv (dropDups env el) prf
keepEnv (z :: zs) (Skip prf) = z :: keepEnv zs prf
-- Corresponds pretty much exactly to 'updateWith'
rebuildEnv : Env new -> Env old -> (prf : SubCtxt sub old) ->
Env (updateWith new old prf)
rebuildEnv new [] SubNil = new
rebuildEnv new [] (InCtxt el p) = absurd el
rebuildEnv [] (x :: xs) (InCtxt el p)
= rebuildEnv [] (dropDups (x :: xs) el) p
rebuildEnv (e :: es) (x :: xs) (InCtxt el p)
= e :: rebuildEnv es (dropDups (x :: xs) el) p
rebuildEnv new (x :: xs) (Skip p) = x :: rebuildEnv new xs p
runST : Env invars -> STrans m a invars outfn ->
((x : a) -> Env (outfn x) -> m b) -> m b
runST env (Pure result) k = k result env
runST env (Bind prog next) k
= runST env prog (\prog', env' => runST env' (next prog') k)
runST env (Lift action) k
= do res <- action
k res env
runST env (New val) k = k MkVar (val :: env)
runST env (Delete lbl prf) k = k () (dropVal prf env)
runST env (DropSubCtxt prf) k = k (dropEnv env prf) (keepEnv env prf)
runST env (Split lbl prf) k = let val = lookupEnv prf env
env' = updateEnv prf env (Value ()) in
k (mkVars val) (addToEnv val env')
where
mkVars : Composite ts -> VarList ts
mkVars CompNil = []
mkVars (CompCons x xs) = MkVar :: mkVars xs
addToEnv : (comp : Composite ts) -> Env xs -> Env (mkCtxt (mkVars comp) ++ xs)
addToEnv CompNil env = env
addToEnv (CompCons x xs) env = x :: addToEnv xs env
runST env (Combine lbl vs prf) k = k () (rebuildVarsIn env prf)
runST env (Call prog ctxt_prf) k
= let env' = dropEnv env ctxt_prf in
runST env' prog
(\prog', envk => k prog' (rebuildEnv envk env ctxt_prf))
runST env (Read lbl prf) k = k (lookupEnv prf env) env
runST env (Write lbl prf val) k = k () (updateEnv prf env val)
export
pure : (result : ty) -> STrans m ty (out_fn result) out_fn
pure = Pure
export
(>>=) : STrans m a st1 st2_fn ->
((result : a) -> STrans m b (st2_fn result) st3_fn) ->
STrans m b st1 st3_fn
(>>=) = Bind
export
lift : Monad m => m t -> STrans m t ctxt (const ctxt)
lift = Lift
export
new : (val : state) ->
STrans m Var ctxt (\lbl => (lbl ::: State state) :: ctxt)
new val = New (Value val)
export
delete : (lbl : Var) ->
{auto prf : InState lbl (State st) ctxt} ->
STrans m () ctxt (const (drop ctxt prf))
delete lbl {prf} = Delete lbl prf
-- Keep only a subset of the current set of resources. Returns the
-- environment corresponding to the dropped portion.
export
dropSubCtxt : {auto prf : SubCtxt ys xs} ->
STrans m (Env ys) xs (const (kept prf))
dropSubCtxt {prf} = DropSubCtxt prf
export
split : (lbl : Var) ->
{auto prf : InState lbl (Composite vars) ctxt} ->
STrans m (VarList vars) ctxt
(\ vs => mkCtxt vs ++
updateCtxt ctxt prf (State ()))
split lbl {prf} = Split lbl prf
export
combine : (comp : Var) -> (vs : List Var) ->
{auto prf : InState comp (State ()) ctxt} ->
{auto var_prf : VarsIn (comp :: vs) ctxt} ->
STrans m () ctxt
(const (combineVarsIn ctxt var_prf))
combine comp vs {var_prf} = Combine comp vs var_prf
export -- implicit ???
call : STrans m t sub new_f ->
{auto ctxt_prf : SubCtxt sub old} ->
STrans m t old (\res => updateWith (new_f res) old ctxt_prf)
call prog {ctxt_prf} = Call prog ctxt_prf
export
read : (lbl : Var) ->
{auto prf : InState lbl (State ty) ctxt} ->
STrans m ty ctxt (const ctxt)
read lbl {prf} = do Value x <- Read lbl prf
pure x
export
write : (lbl : Var) ->
{auto prf : InState lbl ty ctxt} ->
(val : ty') ->
STrans m () ctxt (const (updateCtxt ctxt prf (State ty')))
write lbl {prf} val = Write lbl prf (Value val)
public export %error_reduce
out_res : ty -> (as : List (Action ty)) -> Context
out_res x [] = []
out_res x ((Stable lbl inr) :: xs) = (lbl ::: inr) :: out_res x xs
out_res x ((Trans lbl inr outr) :: xs)
= (lbl ::: outr x) :: out_res x xs
out_res x ((Remove lbl inr) :: xs) = out_res x xs
out_res x (Add outf :: xs) = outf x ++ out_res x xs
public export %error_reduce
in_res : (as : List (Action ty)) -> Context
in_res [] = []
in_res ((Stable lbl inr) :: xs) = (lbl ::: inr) :: in_res xs
in_res ((Trans lbl inr outr) :: xs) = (lbl ::: inr) :: in_res xs
in_res ((Remove lbl inr) :: xs) = (lbl ::: inr) :: in_res xs
in_res (Add outf :: xs) = in_res xs
public export
%error_reduce -- always evaluate this before showing errors
ST : (m : Type -> Type) ->
(ty : Type) ->
List (Action ty) -> Type
ST m ty xs = STrans m ty (in_res xs) (\result : ty => out_res result xs)
-- Console IO is useful sufficiently often that let's have it here
public export
interface ConsoleIO (m : Type -> Type) where
putStr : String -> STrans m () xs (const xs)
getStr : STrans m String xs (const xs)
export
ConsoleIO IO where
putStr str = lift (Interactive.putStr str)
getStr = lift Interactive.getLine
export
run : Applicative m => ST m a [] -> m a
run prog = runST [] prog (\res, env' => pure res)
||| runWith allows running an STrans program with an initial environment,
||| which must be consumed.
||| It's only allowed in the IO monad, because it's inherently unsafe, so
||| we don't want to be able to use it under a 'lift in just *any* ST program -
||| if we have access to an 'Env' we can easily duplicate it - so it's the
||| responsibility of an implementation of an interface in IO which uses it
||| to ensure that it isn't duplicated.
export
runWith : {ctxtf : _} ->
Env ctxt -> STrans IO a ctxt (\res => ctxtf res) ->
IO (res ** Env (ctxtf res))
runWith env prog = runST env prog (\res, env' => pure (res ** env'))
export
runPure : ST Basics.id a [] -> a
runPure prog = runST [] prog (\res, env' => res)
%language ErrorReflection
%error_handler
export
st_precondition : Err -> Maybe (List ErrorReportPart)
st_precondition (CantSolveGoal `(SubCtxt ~sub ~all) _)
= pure
[TextPart "'call' is not valid here. ",
TextPart "The operation has preconditions ",
TermPart sub,
TextPart " which is not a sub set of ",
TermPart all]
st_precondition (CantUnify _ tm1 tm2 _ _ _)
= do reqPre <- getPreconditions tm1
gotPre <- getPreconditions tm2
reqPost <- getPostconditions tm1
gotPost <- getPostconditions tm2
pure $ [TextPart "Error in state transition:"] ++
renderPre gotPre reqPre ++
renderPost gotPost reqPost
where
getPreconditions : TT -> Maybe TT
getPreconditions `(STrans ~m ~ret ~pre ~post) = Just pre
getPreconditions _ = Nothing
getPostconditions : TT -> Maybe TT
getPostconditions `(STrans ~m ~ret ~pre ~post) = Just post
getPostconditions _ = Nothing
renderPre : TT -> TT -> List (ErrorReportPart)
renderPre got req
= [SubReport [TextPart "Operation has preconditions: ",
TermPart req],
SubReport [TextPart "States here are: ",
TermPart got]]
renderPost : TT -> TT -> List (ErrorReportPart)
renderPost got req
= [SubReport [TextPart "Operation has postconditions: ",
TermPart req],
SubReport [TextPart "Required result states here are: ",
TermPart got]]
st_precondition _ = Nothing