mirror of
https://github.com/idris-lang/Idris2.git
synced 2024-12-18 16:51:51 +03:00
266 lines
7.3 KiB
Idris
266 lines
7.3 KiB
Idris
|
module Control.App
|
||
|
|
||
|
import Data.List
|
||
|
import Data.IORef
|
||
|
|
||
|
public export
|
||
|
data Effect : Type where
|
||
|
St : Type -> Effect
|
||
|
Exc : Type -> Effect
|
||
|
Sys : Effect
|
||
|
|
||
|
public export
|
||
|
data HasEff : Effect -> List Effect -> Type where
|
||
|
Here : HasEff e (e :: es)
|
||
|
There : HasEff e es -> HasEff e (e' :: es)
|
||
|
|
||
|
public export
|
||
|
0 Has : List (a -> Type) -> a -> Type
|
||
|
Has [] es = ()
|
||
|
Has [e] es = e es
|
||
|
Has (e :: es') es = (e es, Has es' es)
|
||
|
|
||
|
export
|
||
|
data Env : List Effect -> Type where
|
||
|
None : Env []
|
||
|
Ref : IORef t -> Env es -> Env (St t :: es)
|
||
|
SkipE : Env es -> Env (Exc t :: es)
|
||
|
SkipP : Env es -> Env (Sys :: es)
|
||
|
|
||
|
getState : Env es -> (p : HasEff (St t) es) -> IORef t
|
||
|
getState (Ref r env) Here = r
|
||
|
getState (Ref r env) (There p) = getState env p
|
||
|
getState (SkipE env) (There p) = getState env p
|
||
|
getState (SkipP env) (There p) = getState env p
|
||
|
|
||
|
public export
|
||
|
data Path : Type where
|
||
|
[noHints]
|
||
|
MayThrow : Path
|
||
|
NoThrow : Path
|
||
|
|
||
|
%hint public export
|
||
|
dpath : Path
|
||
|
dpath = MayThrow
|
||
|
|
||
|
data OneOf : List Type -> Path -> Type where
|
||
|
First : e -> OneOf (e :: es) MayThrow
|
||
|
Later : OneOf es MayThrow -> OneOf (e :: es) MayThrow
|
||
|
|
||
|
updateP : OneOf es p -> OneOf es p'
|
||
|
updateP {p=MayThrow} {p'=MayThrow} x = x
|
||
|
|
||
|
Uninhabited (OneOf [] p) where
|
||
|
uninhabited (First x) impossible
|
||
|
uninhabited (Later x) impossible
|
||
|
|
||
|
Uninhabited (OneOf es NoThrow) where
|
||
|
uninhabited (First x) impossible
|
||
|
uninhabited (Later x) impossible
|
||
|
|
||
|
public export
|
||
|
0 excTy : List Effect -> List Type
|
||
|
excTy [] = []
|
||
|
excTy (St t :: es) = excTy es
|
||
|
excTy (Exc e :: es) = e :: excTy es
|
||
|
excTy (Sys :: es) = excTy es
|
||
|
|
||
|
0 execTy : Path -> List Effect -> Type -> Type
|
||
|
execTy p es ty = Either (OneOf (excTy es) p) ty
|
||
|
|
||
|
export
|
||
|
data App : (l : Path) => (es : List Effect) -> Type -> Type where
|
||
|
MkApp : (1 prog : Env e -> IO (execTy l e t)) -> App {l} e t
|
||
|
|
||
|
pureApp : a -> App {l} e a
|
||
|
pureApp x = MkApp (\env => pure (Right x))
|
||
|
|
||
|
public export
|
||
|
data SafeBind : Path -> (l' : Path) -> Type where
|
||
|
[search l']
|
||
|
SafeSame : SafeBind l l
|
||
|
SafeToThrow : SafeBind NoThrow MayThrow
|
||
|
|
||
|
bindApp : SafeBind l l' =>
|
||
|
App {l} e a -> (a -> App {l=l'} e b) -> App {l=l'} e b
|
||
|
bindApp (MkApp prog) k
|
||
|
= MkApp $ \env =>
|
||
|
do Right res <- prog env
|
||
|
| Left err => pure (Left (updateP err))
|
||
|
let MkApp ka = k res
|
||
|
ka env
|
||
|
|
||
|
absurdWith : (1 x : a) -> OneOf e NoThrow -> any
|
||
|
absurdWith x (First p) impossible
|
||
|
|
||
|
export
|
||
|
bindL : App {l=NoThrow} e a -> (1 k : a -> App {l} e b) -> App {l} e b
|
||
|
bindL (MkApp prog) k
|
||
|
= MkApp $ \env =>
|
||
|
io_bind (prog env) $ \r =>
|
||
|
case r of
|
||
|
Left err => absurdWith k err
|
||
|
Right res =>
|
||
|
let MkApp ka = k res in ka env
|
||
|
|
||
|
export
|
||
|
lift : App e t -> App (eff :: e) t
|
||
|
lift (MkApp p)
|
||
|
= MkApp (\env =>
|
||
|
case env of
|
||
|
Ref r env' => p env'
|
||
|
SkipP env' => p env'
|
||
|
SkipE env' =>
|
||
|
do res <- p env'
|
||
|
case res of
|
||
|
Left err => pure (Left (Later err))
|
||
|
Right ok => pure (Right ok))
|
||
|
|
||
|
export
|
||
|
Functor (App {l} es) where
|
||
|
map f ap = bindApp ap $ \ap' => pureApp (f ap')
|
||
|
|
||
|
export
|
||
|
Applicative (App {l} es) where
|
||
|
pure = pureApp
|
||
|
(<*>) f a = bindApp f $ \f' =>
|
||
|
bindApp a $ \a' => pure (f' a')
|
||
|
|
||
|
export
|
||
|
Monad (App es) where
|
||
|
(>>=) = bindApp -- won't get used, but handy to have the instance
|
||
|
|
||
|
export
|
||
|
(>>=) : SafeBind l l' =>
|
||
|
App {l} e a -> (k : a -> App {l=l'} e b) -> App {l=l'} e b
|
||
|
(>>=) = bindApp
|
||
|
|
||
|
export
|
||
|
new : a -> App {l} (St a :: es) t -> App {l} es t
|
||
|
new val (MkApp prog)
|
||
|
= MkApp $ \env =>
|
||
|
do ref <- newIORef val
|
||
|
prog (Ref ref env)
|
||
|
|
||
|
public export
|
||
|
interface State t es where
|
||
|
get : App {l} es t
|
||
|
put : t -> App {l} es ()
|
||
|
|
||
|
export
|
||
|
HasEff (St t) es => State t es where
|
||
|
get
|
||
|
= MkApp $ \env =>
|
||
|
do let ref = getState env %search
|
||
|
val <- readIORef ref
|
||
|
pure (Right val)
|
||
|
put val
|
||
|
= MkApp $ \env =>
|
||
|
do let ref = getState env %search
|
||
|
writeIORef ref val
|
||
|
pure (Right ())
|
||
|
|
||
|
public export
|
||
|
interface Exception e es where
|
||
|
throw : e -> App es a
|
||
|
catch : App es a -> (err : e -> App es a) -> App es a
|
||
|
|
||
|
findException : Env es -> HasEff (Exc e) es -> e -> OneOf (excTy es) MayThrow
|
||
|
findException (SkipE env) Here err = First err
|
||
|
findException (Ref r env) (There p) err = findException env p err
|
||
|
findException (SkipE env) (There p) err = Later $ findException env p err
|
||
|
findException (SkipP env) (There p) err = findException env p err
|
||
|
|
||
|
findError : Env es -> HasEff (Exc e) es -> OneOf (excTy es) MayThrow -> Maybe e
|
||
|
findError (SkipE env) Here (First err) = Just err
|
||
|
findError (SkipE env) Here (Later p) = Nothing -- wrong exception
|
||
|
findError (SkipE env) (There p) (First err) = Nothing -- wrong exception
|
||
|
findError (SkipE env) (There p) (Later q) = findError env p q
|
||
|
findError (Ref r env) (There p) err = findError env p err
|
||
|
findError (SkipP env) (There p) err = findError env p err
|
||
|
|
||
|
export
|
||
|
HasEff (Exc e) es => Exception e es where
|
||
|
throw err
|
||
|
= MkApp $ \env =>
|
||
|
pure (Left (findException env %search err))
|
||
|
catch (MkApp prog) handler
|
||
|
= MkApp $ \env =>
|
||
|
do res <- prog env
|
||
|
case res of
|
||
|
Left err =>
|
||
|
case findError env %search err of
|
||
|
Nothing => pure (Left err)
|
||
|
Just err' =>
|
||
|
let MkApp e' = handler err' in
|
||
|
e' env
|
||
|
Right ok => pure (Right ok)
|
||
|
|
||
|
export
|
||
|
handle : App (Exc err :: e) a ->
|
||
|
(onok : a -> App e b) ->
|
||
|
(onerr : err -> App e b) -> App e b
|
||
|
handle (MkApp prog) onok onerr
|
||
|
= MkApp $ \env =>
|
||
|
do res <- prog (SkipE env)
|
||
|
case res of
|
||
|
Left (First err) =>
|
||
|
let MkApp err' = onerr err in
|
||
|
err' env
|
||
|
Left (Later p) =>
|
||
|
-- different exception, so rethrow
|
||
|
pure (Left p)
|
||
|
Right ok =>
|
||
|
let MkApp ok' = onok ok in
|
||
|
ok' env
|
||
|
|
||
|
public export
|
||
|
interface PrimIO es where
|
||
|
primIO : IO a -> App {l} es a
|
||
|
-- Copies the environment, to make sure states are local to threads
|
||
|
fork : App es () -> App {l} es ()
|
||
|
|
||
|
copyEnv : Env es -> IO (Env es)
|
||
|
copyEnv None = pure None
|
||
|
copyEnv (Ref t env)
|
||
|
= do val <- readIORef t
|
||
|
t' <- newIORef val
|
||
|
env' <- copyEnv env
|
||
|
pure (Ref t' env')
|
||
|
copyEnv (SkipE env)
|
||
|
= do env' <- copyEnv env
|
||
|
pure (SkipE env')
|
||
|
copyEnv (SkipP env)
|
||
|
= do env' <- copyEnv env
|
||
|
pure (SkipP env')
|
||
|
|
||
|
export
|
||
|
HasEff Sys es => PrimIO es where
|
||
|
primIO io
|
||
|
= MkApp $ \env =>
|
||
|
do res <- io
|
||
|
pure (Right res)
|
||
|
fork (MkApp p)
|
||
|
= MkApp $ \env =>
|
||
|
do fork (do env' <- copyEnv env
|
||
|
p env'
|
||
|
pure ())
|
||
|
pure (Right ())
|
||
|
|
||
|
public export
|
||
|
Init : List Effect
|
||
|
Init = [Sys]
|
||
|
|
||
|
export
|
||
|
run : App Init a -> IO a
|
||
|
run (MkApp prog)
|
||
|
= do Right res <- prog (SkipP None)
|
||
|
| Left err => absurd err
|
||
|
pure res
|
||
|
|
||
|
infix 5 @@
|
||
|
|
||
|
public export
|
||
|
data Res : (a : Type) -> (a -> Type) -> Type where
|
||
|
(@@) : (val : a) -> (1 r : t val) -> Res a t
|